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
@@ -4,6 +4,449 @@ class FooNoToS
4
4
  end
5
5
 
6
6
  class TestCurbCurlEasy < Test::Unit::TestCase
7
+ def test_global_reset
8
+ Curl.get($TEST_URL)
9
+ # in a Timeout block you should reset the thread current handle
10
+ Curl.reset
11
+ end
12
+
13
+ def test_nested_easy_methods
14
+ easy = Curl.get(TestServlet.url) {|http|
15
+ res = Curl.get(TestServlet.url + '/not_here')
16
+ assert_equal 404, res.code
17
+ }
18
+ assert_equal 200, easy.code
19
+ end
20
+
21
+ def test_curlopt_resolve
22
+ require 'resolv'
23
+ uri = URI.parse(TestServlet.url)
24
+ resolved_ip = Resolv.getaddress(uri.host) # perform DNS lookup once
25
+ mapping = "#{uri.host}:#{uri.port}:#{resolved_ip}"
26
+
27
+ http = Curl::Easy.new(TestServlet.url)
28
+ http.setopt(Curl::CURLOPT_RESOLVE, [mapping])
29
+
30
+ http.get
31
+
32
+ assert_match(/GET/, http.body)
33
+ end
34
+
35
+ def test_resolve_clear_keeps_handle_usable
36
+ host = "curb.invalid"
37
+ url = "http://#{host}:#{TestServlet.port}#{TestServlet.path}"
38
+ mapping = "#{host}:#{TestServlet.port}:127.0.0.1"
39
+
40
+ http = Curl::Easy.new(url)
41
+ http.dns_cache_timeout = 0
42
+ http.resolve = [mapping]
43
+ http.get
44
+ assert_match(/GET/, http.body)
45
+
46
+ http.resolve = nil
47
+ http.url = TestServlet.url
48
+ http.get
49
+ assert_match(/GET/, http.body)
50
+ end
51
+
52
+ def test_curlopt_stderr_with_file
53
+ # does not work with Tempfile directly
54
+ path = Tempfile.new('curb_test_curlopt_stderr').path
55
+ File.open(path, 'w') do |file|
56
+ easy = Curl::Easy.new(TestServlet.url)
57
+ easy.verbose = true
58
+ easy.setopt(Curl::CURLOPT_STDERR, file)
59
+ easy.perform
60
+ end
61
+ output = File.read(path)
62
+
63
+ assert_match(/HTTP\/1\.1\ 200\ OK(?:\ )?/, output)
64
+ assert_match('Host: 127.0.0.1:9129', output)
65
+ end
66
+
67
+ def test_curlopt_stderr_with_io
68
+ tmp = Tempfile.new('curb_test_curlopt_stderr')
69
+ path = tmp.path
70
+ fd = IO.sysopen(path, 'w')
71
+ io = IO.new(fd, 'w')
72
+ io.sync = true
73
+
74
+ easy = Curl::Easy.new(TestServlet.url)
75
+ easy.verbose = true
76
+ easy.setopt(Curl::CURLOPT_STDERR, io)
77
+ easy.perform
78
+
79
+ io.flush
80
+ io.close
81
+ output = File.read(path)
82
+
83
+ assert_match(/HTTP\/1\.1\ 200\ OK(?:\ )?/, output)
84
+ assert_match('Host: 127.0.0.1:9129', output)
85
+ ensure
86
+ tmp.close! if defined?(tmp) && tmp
87
+ end
88
+
89
+ def test_curlopt_stderr_gc_safe
90
+ tmp = Tempfile.new('curb_test_curlopt_stderr_gc')
91
+ path = tmp.path
92
+ fd = IO.sysopen(path, 'w')
93
+ io = IO.new(fd, 'w')
94
+
95
+ easy = Curl::Easy.new(TestServlet.url)
96
+ easy.verbose = true
97
+ easy.setopt(Curl::CURLOPT_STDERR, io)
98
+
99
+ # Drop our Ruby reference and force GC; curb should retain it internally
100
+ io = nil
101
+ GC.start
102
+
103
+ easy.perform
104
+
105
+ output = File.read(path)
106
+ assert_match(/HTTP\/1\.1\ 200\ OK(?:\ )?/, output)
107
+ assert_match('Host: 127.0.0.1:9129', output)
108
+ ensure
109
+ tmp.close! if defined?(tmp) && tmp
110
+ end
111
+
112
+ def test_head_request_no_body_and_no_timeout
113
+ # Ensure a HEAD request completes and does not attempt to read a body
114
+ # even when the server advertises a Content-Length.
115
+ easy = nil
116
+ assert_nothing_raised do
117
+ easy = Curl.http(:HEAD, TestServlet.url)
118
+ end
119
+ assert_not_nil easy
120
+ # Header string should contain the HTTP status line.
121
+ assert_match(/HTTP\/1\.1\s\d+\s/, easy.header_str.to_s)
122
+ # Body should be empty for HEAD requests (libcurl won't call the write callback).
123
+ assert_equal "", easy.body_str.to_s
124
+ end
125
+
126
+ def test_head_request_restores_easy_state_after_callback_exception
127
+ easy = Curl::Easy.new(TestServlet.url)
128
+ easy.on_complete { raise "head complete blew up" }
129
+
130
+ error = assert_raise(Curl::Err::AbortedByCallbackError) { easy.http("HEAD") }
131
+ assert_equal "head complete blew up", error.message
132
+
133
+ easy.on_complete { |_curl| }
134
+ easy.perform
135
+
136
+ assert_equal "GET", easy.body_str
137
+ end
138
+
139
+ def test_patch_request_does_not_leak_custom_method_to_next_perform
140
+ easy = Curl::Easy.new(TestServlet.url)
141
+
142
+ easy.http_patch("a=b")
143
+ assert_equal "PATCH\na=b", easy.body_str
144
+
145
+ easy.perform
146
+ assert_equal "GET", easy.body_str
147
+ end
148
+
149
+ def test_put_request_does_not_leak_custom_method_to_next_perform
150
+ easy = Curl::Easy.new(TestServlet.url)
151
+
152
+ easy.http_put("payload")
153
+ assert_equal "PUT\npayload", easy.body_str
154
+
155
+ easy.perform
156
+ assert_equal "GET", easy.body_str
157
+ end
158
+
159
+ def test_failed_put_data_setup_does_not_leave_easy_in_upload_mode
160
+ easy = Curl::Easy.new(TestServlet.url)
161
+ easy.headers = ["X-Test: true"]
162
+
163
+ assert_raise(RuntimeError) { easy.put_data = "payload" }
164
+
165
+ easy.headers = {}
166
+ easy.perform
167
+ assert_equal "GET", easy.body_str
168
+ end
169
+
170
+ def test_perform_releases_failed_implicit_multi_without_follow_up_easy_perform
171
+ Curl::Easy.flush_deferred_multi_closes
172
+ assert_equal 0, Curl::Easy.deferred_multi_closes.length
173
+
174
+ easy, implicit_multi = capture_failed_implicit_multi_cleanup_state
175
+
176
+ assert_nil easy.multi
177
+ assert_equal({}, implicit_multi.requests)
178
+ assert_equal false, implicit_multi.instance_variable_get(:@deferred_close)
179
+ assert_equal 0, Curl::Easy.deferred_multi_closes.length
180
+ ensure
181
+ easy.multi = nil if defined?(easy) && easy
182
+ implicit_multi.close if defined?(implicit_multi) && implicit_multi
183
+ Curl::Easy.flush_deferred_multi_closes
184
+ end
185
+
186
+ def test_deferred_multi_closes_stay_with_their_owner_thread_across_retry
187
+ Curl::Easy.flush_deferred_multi_closes(all_threads: true)
188
+ multi = deferred_close_stub_multi(failures: 1)
189
+
190
+ Curl::Easy.defer_multi_close(multi, nil)
191
+
192
+ Curl::Easy.flush_deferred_multi_closes
193
+ assert_equal 1, multi.close_attempts
194
+ assert_equal true, multi.instance_variable_get(:@deferred_close)
195
+ assert_equal 1, Curl::Easy.deferred_multi_closes.length
196
+
197
+ Thread.new { Curl::Easy.flush_deferred_multi_closes }.join
198
+ assert_equal 1, multi.close_attempts
199
+ assert_equal true, multi.instance_variable_get(:@deferred_close)
200
+ assert_equal 1, Curl::Easy.deferred_multi_closes.length
201
+
202
+ Curl::Easy.flush_deferred_multi_closes
203
+ assert_equal 2, multi.close_attempts
204
+ assert_equal true, multi.closed?
205
+ assert_equal false, multi.instance_variable_get(:@deferred_close)
206
+ assert_equal 0, Curl::Easy.deferred_multi_closes.length
207
+ ensure
208
+ Curl::Easy.flush_deferred_multi_closes(all_threads: true)
209
+ end
210
+
211
+ def capture_created_multis
212
+ created_multis = []
213
+ multi_singleton = class << Curl::Multi; self; end
214
+
215
+ multi_singleton.__send__(:alias_method, :__curb_test_original_new, :new)
216
+ multi_singleton.__send__(:define_method, :new) do |*args, &block|
217
+ multi = __curb_test_original_new(*args, &block)
218
+ created_multis << multi
219
+ multi
220
+ end
221
+
222
+ yield created_multis
223
+ ensure
224
+ if defined?(multi_singleton) && multi_singleton.method_defined?(:__curb_test_original_new)
225
+ multi_singleton.__send__(:remove_method, :new)
226
+ multi_singleton.__send__(:alias_method, :new, :__curb_test_original_new)
227
+ multi_singleton.__send__(:remove_method, :__curb_test_original_new)
228
+ end
229
+ end
230
+
231
+ def capture_failed_implicit_multi_cleanup_state
232
+ created_multis = nil
233
+ easy = nil
234
+
235
+ capture_created_multis do |captured_multis|
236
+ created_multis = captured_multis
237
+ easy = Curl::Easy.new(TestServlet.url)
238
+ easy.on_complete { raise "complete blew up" }
239
+
240
+ error = assert_raise(Curl::Err::AbortedByCallbackError) { easy.perform }
241
+ assert_equal "complete blew up", error.message
242
+ end
243
+
244
+ assert_equal 1, created_multis.length, "test should observe one implicit multi created by Easy#perform"
245
+
246
+ [easy, created_multis.first]
247
+ end
248
+
249
+ def deferred_close_stub_multi(failures:)
250
+ Object.new.tap do |multi|
251
+ requests = {}
252
+ remaining_failures = failures
253
+ close_attempts = 0
254
+ closed = false
255
+
256
+ multi.define_singleton_method(:requests) { requests }
257
+ multi.define_singleton_method(:_close) do
258
+ close_attempts += 1
259
+ if remaining_failures.positive?
260
+ remaining_failures -= 1
261
+ raise "close failed"
262
+ end
263
+
264
+ closed = true
265
+ end
266
+ multi.define_singleton_method(:close_attempts) { close_attempts }
267
+ multi.define_singleton_method(:closed?) { closed }
268
+ end
269
+ end
270
+
271
+ def test_perform_restores_explicit_multi_after_callback_error
272
+ multi = Curl::Multi.new
273
+ easy = Curl::Easy.new(TestServlet.url)
274
+ easy.multi = multi
275
+ easy.on_complete { raise "complete blew up" }
276
+
277
+ error = assert_raise(Curl::Err::AbortedByCallbackError) { easy.perform }
278
+ assert_equal "complete blew up", error.message
279
+ assert_same multi, easy.multi
280
+ assert_equal({}, multi.requests)
281
+
282
+ easy.on_complete { |_curl| }
283
+ easy.perform
284
+
285
+ assert_same multi, easy.multi
286
+ assert_equal "GET", easy.body_str
287
+ ensure
288
+ easy.multi = nil if defined?(easy) && easy && easy.multi.equal?(multi)
289
+ multi.close if defined?(multi) && multi
290
+ end
291
+
292
+ def test_perform_restores_cached_implicit_multi_after_callback_error
293
+ Curl::Multi.autoclose = false
294
+
295
+ easy = Curl::Easy.new(TestServlet.url)
296
+ easy.perform
297
+
298
+ cached_multi = easy.multi
299
+ easy.on_complete { raise "complete blew up" }
300
+
301
+ error = assert_raise(Curl::Err::AbortedByCallbackError) { easy.perform }
302
+ assert_equal "complete blew up", error.message
303
+ assert_same cached_multi, easy.multi
304
+ assert_equal({}, cached_multi.requests)
305
+
306
+ easy.on_complete { |_curl| }
307
+ easy.perform
308
+
309
+ assert_same cached_multi, easy.multi
310
+ assert_equal "GET", easy.body_str
311
+ ensure
312
+ easy.multi = nil if defined?(easy) && easy
313
+ cached_multi.close if defined?(cached_multi) && cached_multi
314
+ Curl::Multi.autoclose = false
315
+ end
316
+
317
+ def test_close_clears_explicit_idle_easy_multi_reference
318
+ easy = Curl::Easy.new($TEST_URL)
319
+ multi = Curl::Multi.new
320
+ easy.multi = multi
321
+
322
+ assert_same multi, easy.multi
323
+ assert_equal({}, multi.requests)
324
+
325
+ multi.close
326
+
327
+ assert_nil easy.multi
328
+ end
329
+
330
+ def test_close_clears_cached_idle_easy_multi_reference
331
+ Curl::Multi.autoclose = false
332
+
333
+ easy = Curl::Easy.new($TEST_URL)
334
+ easy.perform
335
+
336
+ cached_multi = easy.multi
337
+ assert_not_nil cached_multi
338
+ assert_equal({}, cached_multi.requests)
339
+
340
+ cached_multi.close
341
+
342
+ assert_nil easy.multi
343
+ ensure
344
+ easy.multi = nil if defined?(easy) && easy
345
+ Curl::Multi.autoclose = false
346
+ end
347
+
348
+ def test_multi_assignment_rejects_non_multi_objects
349
+ easy = Curl::Easy.new($TEST_URL)
350
+
351
+ error = assert_raise(TypeError) { easy.multi = Object.new }
352
+
353
+ assert_match(/Curl::Multi/, error.message)
354
+ assert_nil easy.multi
355
+ end
356
+
357
+ def test_perform_can_reuse_explicit_multi_when_autoclose_is_enabled
358
+ Curl::Multi.autoclose = true
359
+
360
+ easy = Curl::Easy.new(TestServlet.url)
361
+ multi = Curl::Multi.new
362
+
363
+ easy.multi = multi
364
+ easy.perform
365
+
366
+ assert_nil easy.multi
367
+ assert_equal({}, multi.requests)
368
+
369
+ easy.multi = multi
370
+ easy.perform
371
+
372
+ assert_nil easy.multi
373
+ assert_equal({}, multi.requests)
374
+ assert_equal "GET", easy.body_str
375
+ ensure
376
+ easy.multi = nil if defined?(easy) && easy
377
+ multi.close if defined?(multi) && multi
378
+ Curl::Multi.autoclose = false
379
+ end
380
+
381
+ def test_perform_reraises_on_body_exception_for_frozen_easy
382
+ easy = Curl::Easy.new($TEST_URL)
383
+ callback_error = Class.new(RuntimeError)
384
+ easy.on_body { raise callback_error, "body blew up" }
385
+ easy.freeze
386
+
387
+ error = assert_raise(callback_error) { easy.perform }
388
+
389
+ assert_equal "body blew up", error.message
390
+ end
391
+
392
+ def test_perform_preserves_implicit_multi_when_autoclose_is_disabled
393
+ Curl::Multi.autoclose = false
394
+
395
+ easy = Curl::Easy.new(TestServlet.url)
396
+ easy.perform
397
+
398
+ first_multi = easy.multi
399
+ assert_not_nil first_multi
400
+ assert_equal({}, first_multi.requests)
401
+
402
+ easy.perform
403
+
404
+ assert_same first_multi, easy.multi
405
+ assert_equal "GET", easy.body_str
406
+ ensure
407
+ easy.multi = nil if defined?(easy) && easy
408
+ first_multi.close if defined?(first_multi) && first_multi
409
+ Curl::Multi.autoclose = false
410
+ end
411
+
412
+ def test_curlopt_stderr_fails_with_tempdir
413
+ Tempfile.open('curb_test_curlopt_stderr') do |tempfile|
414
+ easy = Curl::Easy.new(TestServlet.url)
415
+
416
+ assert_raise(TypeError) do
417
+ easy.setopt(Curl::CURLOPT_STDERR, tempfile)
418
+ end
419
+ end
420
+ end
421
+
422
+ def test_curlopt_stderr_fails_with_stringio
423
+ stringio = StringIO.new
424
+ easy = Curl::Easy.new(TestServlet.url)
425
+
426
+ assert_raise(TypeError) do
427
+ easy.setopt(Curl::CURLOPT_STDERR, stringio)
428
+ end
429
+ end
430
+
431
+ def test_curlopt_stderr_fails_with_string
432
+ string = String.new
433
+ easy = Curl::Easy.new(TestServlet.url)
434
+
435
+ assert_raise(TypeError) do
436
+ easy.setopt(Curl::CURLOPT_STDERR, string)
437
+ end
438
+ end
439
+
440
+ def test_exception
441
+ begin
442
+ Curl.get('NOT_FOUND_URL')
443
+ rescue
444
+ assert true
445
+ rescue Exception
446
+ assert false, "We should raise StandardError"
447
+ end
448
+ end
449
+
7
450
  def test_threads
8
451
  t = []
9
452
  5.times do
@@ -11,31 +454,30 @@ class TestCurbCurlEasy < Test::Unit::TestCase
11
454
  5.times do
12
455
  c = Curl.get($TEST_URL)
13
456
  assert_match(/^# DO NOT REMOVE THIS COMMENT/, c.body_str)
14
- assert_equal "", c.header_str
457
+ assert_match(/^# DO NOT REMOVE THIS COMMENT/, c.body)
15
458
  end
16
459
  end
17
460
  end
18
461
 
19
- t.each {|t| t.join }
462
+ t.each {|x| x.join }
20
463
  end
21
464
 
22
465
  def test_class_perform_01
23
466
  assert_instance_of Curl::Easy, c = Curl::Easy.perform($TEST_URL)
24
467
  assert_match(/^# DO NOT REMOVE THIS COMMENT/, c.body_str)
25
- assert_equal "", c.header_str
468
+ assert_match(/^# DO NOT REMOVE THIS COMMENT/, c.body)
26
469
  end
27
470
 
28
471
  def test_class_perform_02
29
- data = ""
472
+ data = String.new
30
473
  assert_instance_of Curl::Easy, c = Curl::Easy.perform($TEST_URL) { |curl| curl.on_body { |d| data << d; d.length } }
31
474
 
32
475
  assert_nil c.body_str
33
- assert_equal "", c.header_str
34
476
  assert_match(/^# DO NOT REMOVE THIS COMMENT/, data)
35
477
  end
36
478
 
37
479
  def test_class_perform_03
38
- assert_raise(Curl::Err::CouldntReadError) { c = Curl::Easy.perform($TEST_URL + "nonexistent") }
480
+ assert_raise(Curl::Err::CouldntReadError) { Curl::Easy.perform($TEST_URL + "nonexistent") }
39
481
  end
40
482
 
41
483
  def test_new_01
@@ -125,11 +567,10 @@ class TestCurbCurlEasy < Test::Unit::TestCase
125
567
  c = Curl::Easy.new($TEST_URL)
126
568
  assert_equal true, c.http_get
127
569
  assert_match(/^# DO NOT REMOVE THIS COMMENT/, c.body_str)
128
- assert_equal "", c.header_str
129
570
  end
130
571
 
131
572
  def test_get_02
132
- data = ""
573
+ data = String.new
133
574
  c = Curl::Easy.new($TEST_URL) do |curl|
134
575
  curl.on_body { |d| data << d; d.length }
135
576
  end
@@ -137,7 +578,6 @@ class TestCurbCurlEasy < Test::Unit::TestCase
137
578
  assert_equal true, c.http_get
138
579
 
139
580
  assert_nil c.body_str
140
- assert_equal "", c.header_str
141
581
  assert_match(/^# DO NOT REMOVE THIS COMMENT/, data)
142
582
  end
143
583
 
@@ -150,6 +590,7 @@ class TestCurbCurlEasy < Test::Unit::TestCase
150
590
 
151
591
 
152
592
  def test_last_effective_url_01
593
+ omit('Windows file URL semantics differ') if WINDOWS
153
594
  c = Curl::Easy.new($TEST_URL)
154
595
 
155
596
  assert_equal $TEST_URL, c.url
@@ -320,19 +761,71 @@ class TestCurbCurlEasy < Test::Unit::TestCase
320
761
  c.max_redirects = nil
321
762
  assert_nil c.max_redirects
322
763
  end
323
-
764
+
765
+ def test_timeout_with_floats
766
+ c = Curl::Easy.new($TEST_URL)
767
+
768
+ c.timeout = 1.5
769
+ assert_equal 1500, c.timeout_ms
770
+ assert_equal 1.5, c.timeout
771
+ end
772
+
773
+ def test_timeout_with_negative
774
+ c = Curl::Easy.new($TEST_URL)
775
+
776
+ c.timeout = -1.5
777
+ assert_equal 0, c.timeout
778
+ assert_equal 0, c.timeout_ms
779
+
780
+ c.timeout = -4.8
781
+ assert_equal 0, c.timeout
782
+ assert_equal 0, c.timeout_ms
783
+ end
784
+
324
785
  def test_timeout_01
325
786
  c = Curl::Easy.new($TEST_URL)
326
-
327
- assert_nil c.timeout
328
-
787
+
788
+ assert_equal 0, c.timeout
789
+
329
790
  c.timeout = 3
330
791
  assert_equal 3, c.timeout
331
-
332
- c.timeout = nil
333
- assert_nil c.timeout
792
+
793
+ c.timeout = 0
794
+ assert_equal 0, c.timeout
334
795
  end
335
-
796
+
797
+ def test_timeout_ms_01
798
+ c = Curl::Easy.new($TEST_URL)
799
+
800
+ assert_equal 0, c.timeout_ms
801
+
802
+ c.timeout_ms = 100
803
+ assert_equal 100, c.timeout_ms
804
+
805
+ c.timeout_ms = nil
806
+ assert_equal 0, c.timeout_ms
807
+ end
808
+
809
+ def test_timeout_ms_with_floats
810
+ c = Curl::Easy.new($TEST_URL)
811
+
812
+ c.timeout_ms = 55.5
813
+ assert_equal 55, c.timeout_ms
814
+ assert_equal 0.055, c.timeout
815
+ end
816
+
817
+ def test_timeout_ms_with_negative
818
+ c = Curl::Easy.new($TEST_URL)
819
+
820
+ c.timeout_ms = -1.5
821
+ assert_equal 0, c.timeout
822
+ assert_equal 0, c.timeout_ms
823
+
824
+ c.timeout_ms = -4.8
825
+ assert_equal 0, c.timeout
826
+ assert_equal 0, c.timeout_ms
827
+ end
828
+
336
829
  def test_connect_timeout_01
337
830
  c = Curl::Easy.new($TEST_URL)
338
831
 
@@ -344,7 +837,19 @@ class TestCurbCurlEasy < Test::Unit::TestCase
344
837
  c.connect_timeout = nil
345
838
  assert_nil c.connect_timeout
346
839
  end
347
-
840
+
841
+ def test_connect_timeout_ms_01
842
+ c = Curl::Easy.new($TEST_URL)
843
+
844
+ assert_nil c.connect_timeout_ms
845
+
846
+ c.connect_timeout_ms = 100
847
+ assert_equal 100, c.connect_timeout_ms
848
+
849
+ c.connect_timeout_ms = nil
850
+ assert_nil c.connect_timeout_ms
851
+ end
852
+
348
853
  def test_ftp_response_timeout_01
349
854
  c = Curl::Easy.new($TEST_URL)
350
855
 
@@ -534,6 +1039,27 @@ class TestCurbCurlEasy < Test::Unit::TestCase
534
1039
  assert_raises(ArgumentError) { c.resolve_mode = :bad }
535
1040
  end
536
1041
 
1042
+ def test_http_version_accessors
1043
+ c = Curl::Easy.new
1044
+ assert_equal Curl::HTTP_NONE, c.http_version
1045
+
1046
+ c.http_version = Curl::HTTP_1_1
1047
+ assert_equal Curl::HTTP_1_1, c.http_version
1048
+
1049
+ if Curl.const_defined?(:HTTP_2TLS)
1050
+ c.http_version = Curl::HTTP_2TLS
1051
+ assert_equal Curl::HTTP_2TLS, c.http_version
1052
+ end
1053
+
1054
+ if Curl.const_defined?(:HTTP_2_PRIOR_KNOWLEDGE)
1055
+ c.http_version = Curl::HTTP_2_PRIOR_KNOWLEDGE
1056
+ assert_equal Curl::HTTP_2_PRIOR_KNOWLEDGE, c.http_version
1057
+ end
1058
+
1059
+ c.http_version = nil
1060
+ assert_equal Curl::HTTP_NONE, c.http_version
1061
+ end
1062
+
537
1063
  def test_enable_cookies
538
1064
  c = Curl::Easy.new
539
1065
  assert !c.enable_cookies?
@@ -562,14 +1088,23 @@ class TestCurbCurlEasy < Test::Unit::TestCase
562
1088
  assert_equal "some.file", c.cookiejar
563
1089
  end
564
1090
 
1091
+ def test_cookielist
1092
+ c = Curl::Easy.new TestServlet.url
1093
+ c.enable_cookies = true
1094
+ c.post_body = URI.encode_www_form('c' => 'somename=somevalue')
1095
+ assert_nil c.cookielist
1096
+ c.perform
1097
+ assert_match(/somevalue/, c.cookielist.join(''))
1098
+ end
1099
+
565
1100
  def test_on_success
566
1101
  curl = Curl::Easy.new($TEST_URL)
567
1102
  on_success_called = false
568
1103
  curl.on_success {|c|
569
1104
  on_success_called = true
570
- assert_not_nil c.body_str
571
- assert_equal "", c.header_str
572
- assert_match(/^# DO NOT REMOVE THIS COMMENT/, c.body_str)
1105
+ assert_not_nil c.body
1106
+ assert_match(/Content-Length: /, c.head)
1107
+ assert_match(/^# DO NOT REMOVE THIS COMMENT/, c.body)
573
1108
  }
574
1109
  curl.perform
575
1110
  assert on_success_called, "Success handler not called"
@@ -611,20 +1146,27 @@ class TestCurbCurlEasy < Test::Unit::TestCase
611
1146
  assert_equal 'GET', curl.body_str
612
1147
  end
613
1148
 
614
- def test_post_remote
1149
+ def test_remote
615
1150
  curl = Curl::Easy.new(TestServlet.url)
616
1151
  curl.http_post([Curl::PostField.content('document_id', 5)])
617
1152
  assert_equal "POST\ndocument_id=5", curl.unescape(curl.body_str)
1153
+
1154
+ curl.http_put([Curl::PostField.content('document_id', 5)])
1155
+ assert_equal "PUT\ndocument_id=5", curl.unescape(curl.body_str)
1156
+
1157
+ curl.http_patch([Curl::PostField.content('document_id', 5)])
1158
+ assert_equal "PATCH\ndocument_id=5", curl.unescape(curl.body_str)
618
1159
  end
619
1160
 
620
- def test_post_remote_is_easy_handle
1161
+ def test_remote_is_easy_handle
621
1162
  # see: http://pastie.org/560852 and
622
1163
  # http://groups.google.com/group/curb---ruby-libcurl-bindings/browse_thread/thread/216bb2d9b037f347?hl=en
623
- [:post, :get, :head, :delete].each do |method|
1164
+ [:post, :patch, :put, :get, :head, :delete].each do |method|
624
1165
  retries = 0
625
1166
  begin
626
1167
  count = 0
627
- curl = Curl::Easy.send("http_#{method}", TestServlet.url) do|c|
1168
+ m = "http_#{method}".to_sym
1169
+ Curl::Easy.send(m, TestServlet.url) do|c|
628
1170
  count += 1
629
1171
  assert_equal Curl::Easy, c.class
630
1172
  end
@@ -633,24 +1175,40 @@ class TestCurbCurlEasy < Test::Unit::TestCase
633
1175
  retries+=1
634
1176
  retry if retries < 3
635
1177
  raise e
1178
+ rescue ArgumentError => e
1179
+ assert false, "#{m} - #{e.message}"
636
1180
  end
637
1181
  end
638
1182
  end
639
1183
 
640
1184
  # see: https://github.com/rvanlieshout/curb/commit/8bcdefddc0162484681ebd1a92d52a642666a445
641
- def test_post_multipart_array_remote
1185
+ def test_multipart_array_remote
642
1186
  curl = Curl::Easy.new(TestServlet.url)
643
1187
  curl.multipart_form_post = true
644
1188
  fields = [
645
- Curl::PostField.file('foo', File.expand_path(File.join(File.dirname(__FILE__),'..','README'))),
646
- Curl::PostField.file('bar', File.expand_path(File.join(File.dirname(__FILE__),'..','README')))
1189
+ Curl::PostField.file('foo', File.expand_path(File.join(File.dirname(__FILE__),'..','README.md'))),
1190
+ Curl::PostField.file('bar', File.expand_path(File.join(File.dirname(__FILE__),'..','README.md')))
647
1191
  ]
648
1192
  curl.http_post(fields)
649
- assert_match /HTTP POST file upload/, curl.body_str
650
- assert_match /Content-Disposition: form-data/, curl.body_str
1193
+ assert_match(/HTTP POST file upload/, curl.body_str)
1194
+ assert_match(/Content-Disposition: form-data/, curl.body_str)
1195
+
1196
+ curl = Curl::Easy.new(TestServlet.url)
1197
+ curl.multipart_form_post = true
1198
+ fields = [
1199
+ Curl::PostField.file('foo', File.expand_path(File.join(File.dirname(__FILE__),'..','README.md'))),
1200
+ Curl::PostField.file('bar', File.expand_path(File.join(File.dirname(__FILE__),'..','README.md')))
1201
+ ]
1202
+ curl.http_put(fields)
1203
+ assert_match(/HTTP POST file upload/, curl.body_str)
1204
+ assert_match(/Content-Disposition: form-data/, curl.body_str)
1205
+
1206
+ curl.http_patch(fields)
1207
+ assert_match(/HTTP POST file upload/, curl.body_str)
1208
+ assert_match(/Content-Disposition: form-data/, curl.body_str)
651
1209
  end
652
1210
 
653
- def test_post_with_body_remote
1211
+ def test_with_body_remote
654
1212
  curl = Curl::Easy.new(TestServlet.url)
655
1213
  curl.post_body = 'foo=bar&encoded%20string=val'
656
1214
 
@@ -658,23 +1216,151 @@ class TestCurbCurlEasy < Test::Unit::TestCase
658
1216
 
659
1217
  assert_equal "POST\nfoo=bar&encoded%20string=val", curl.body_str
660
1218
  assert_equal 'foo=bar&encoded%20string=val', curl.post_body
1219
+
1220
+ curl = Curl::Easy.new(TestServlet.url)
1221
+ curl.put_data = 'foo=bar&encoded%20string=val'
1222
+
1223
+ curl.perform
1224
+
1225
+ assert_equal "PUT\nfoo=bar&encoded%20string=val", curl.body_str
661
1226
  end
662
-
663
- def test_form_post_body_remote
1227
+
1228
+ def test_post_body_from_to_s_keeps_string_buffer_alive
1229
+ post_body = Object.new
1230
+ def post_body.to_s
1231
+ 'foo=bar&encoded%20string=val'
1232
+ end
1233
+
1234
+ curl = Curl::Easy.new(TestServlet.url)
1235
+ curl.post_body = post_body
1236
+
1237
+ assert_equal 'foo=bar&encoded%20string=val', curl.post_body
1238
+
1239
+ GC.start
1240
+ GC.compact if GC.respond_to?(:compact)
1241
+
1242
+ curl.perform
1243
+ assert_equal "POST\nfoo=bar&encoded%20string=val", curl.body_str
1244
+ end
1245
+
1246
+ def test_post_body_preserves_assigned_snapshot_when_original_string_is_mutated
1247
+ omit('CURLOPT_COPYPOSTFIELDS is not available in this build') unless Curl.const_defined?(:CURLOPT_COPYPOSTFIELDS)
1248
+
1249
+ curl = Curl::Easy.new(TestServlet.url)
1250
+ post_body = String.new('alpha=1')
1251
+ curl.post_body = post_body
1252
+
1253
+ post_body.replace('beta=2')
1254
+
1255
+ assert_equal 'beta=2', post_body
1256
+ assert_equal 'alpha=1', curl.post_body
1257
+
1258
+ curl.perform
1259
+
1260
+ assert_equal "POST\nalpha=1", curl.body_str
1261
+ assert_equal 'alpha=1', curl.post_body
1262
+ end
1263
+
1264
+ def test_setopt_postfields_keeps_string_buffer_alive
1265
+ curl = Curl::Easy.new(TestServlet.url)
1266
+ curl.set(Curl::CURLOPT_POSTFIELDS, 'foo=bar&encoded%20string=val')
1267
+
1268
+ assert_equal 'foo=bar&encoded%20string=val', curl.post_body
1269
+
1270
+ GC.start
1271
+ GC.compact if GC.respond_to?(:compact)
1272
+
1273
+ curl.perform
1274
+ assert_equal "POST\nfoo=bar&encoded%20string=val", curl.body_str
1275
+ end
1276
+
1277
+ def test_setopt_postfields_preserves_assigned_snapshot_when_original_string_is_mutated
1278
+ omit('CURLOPT_COPYPOSTFIELDS is not available in this build') unless Curl.const_defined?(:CURLOPT_COPYPOSTFIELDS)
1279
+
1280
+ curl = Curl::Easy.new(TestServlet.url)
1281
+ post_body = String.new('alpha=1')
1282
+ curl.set(Curl::CURLOPT_POSTFIELDS, post_body)
1283
+
1284
+ post_body.replace('beta=2')
1285
+
1286
+ assert_equal 'beta=2', post_body
1287
+ assert_equal 'alpha=1', curl.post_body
1288
+
1289
+ curl.perform
1290
+
1291
+ assert_equal "POST\nalpha=1", curl.body_str
1292
+ assert_equal 'alpha=1', curl.post_body
1293
+ end
1294
+
1295
+ def test_setopt_postfields_nil_preserves_post_request
1296
+ curl = Curl::Easy.new(TestServlet.url)
1297
+ curl.set(Curl::CURLOPT_POST, true)
1298
+ curl.set(Curl::CURLOPT_POSTFIELDS, nil)
1299
+
1300
+ curl.perform
1301
+
1302
+ assert_nil curl.post_body
1303
+ assert_equal "POST\n", curl.body_str
1304
+ end
1305
+
1306
+ def test_form_body_remote
664
1307
  curl = Curl::Easy.new(TestServlet.url)
665
1308
  curl.http_post('foo=bar', 'encoded%20string=val')
666
1309
 
667
1310
  assert_equal "POST\nfoo=bar&encoded%20string=val", curl.body_str
668
1311
  assert_equal 'foo=bar&encoded%20string=val', curl.post_body
1312
+
1313
+ curl = Curl::Easy.new(TestServlet.url)
1314
+ curl.http_put('foo=bar', 'encoded%20string=val')
1315
+
1316
+ assert_equal "PUT\nfoo=bar&encoded%20string=val", curl.body_str
1317
+
1318
+ curl = Curl::Easy.new(TestServlet.url)
1319
+ curl.http_patch('foo=bar', 'encoded%20string=val')
1320
+
1321
+ assert_equal "PATCH\nfoo=bar&encoded%20string=val", curl.body_str
669
1322
  end
670
1323
 
671
- def test_post_multipart_file_remote
1324
+ def test_multipart_file_remote
1325
+ [:put, :post, :patch].each {|method|
1326
+ curl = Curl::Easy.new(TestServlet.url)
1327
+ curl.multipart_form_post = true
1328
+ pf = Curl::PostField.file('readme', File.expand_path(File.join(File.dirname(__FILE__),'..','README.md')))
1329
+ curl.send("http_#{method}", pf)
1330
+ assert_match(/HTTP POST file upload/, curl.body_str)
1331
+ assert_match(/Content-Disposition: form-data/, curl.body_str)
1332
+ }
1333
+ end
1334
+
1335
+ def test_multipart_form_reuse_clears_previous_form
672
1336
  curl = Curl::Easy.new(TestServlet.url)
673
1337
  curl.multipart_form_post = true
674
- pf = Curl::PostField.file('readme', File.expand_path(File.join(File.dirname(__FILE__),'..','README')))
675
- curl.http_post(pf)
676
- assert_match /HTTP POST file upload/, curl.body_str
677
- assert_match /Content-Disposition: form-data/, curl.body_str
1338
+ curl.http_post(Curl::PostField.content('document_id', '5'))
1339
+ assert_match(/document_id/, curl.body_str)
1340
+
1341
+ curl.multipart_form_post = false
1342
+ curl.post_body = 'foo=bar'
1343
+ curl.http_post
1344
+
1345
+ assert_equal "POST\nfoo=bar", curl.body_str
1346
+ end
1347
+
1348
+ def test_multipart_patch_build_failure_restores_easy_request_state
1349
+ curl = Curl::Easy.new(TestServlet.url)
1350
+ curl.multipart_form_post = true
1351
+ field = Curl::PostField.content('document_id', '5')
1352
+
1353
+ field.name = nil
1354
+ error = assert_raise(Curl::Err::InvalidPostFieldError) do
1355
+ # The first field allocates native multipart form state before the second field raises.
1356
+ curl.http_patch(Curl::PostField.content('keep', 'allocated'), field)
1357
+ end
1358
+ assert_match(/Cannot post unnamed field/, error.message)
1359
+
1360
+ curl.multipart_form_post = false
1361
+ curl.perform
1362
+
1363
+ assert_equal 'GET', curl.body_str
678
1364
  end
679
1365
 
680
1366
  def test_delete_remote
@@ -696,7 +1382,7 @@ class TestCurbCurlEasy < Test::Unit::TestCase
696
1382
  redirect = curl.header_str.match(/Location: (.*)/)
697
1383
 
698
1384
  assert_equal '', curl.body_str
699
- assert_match '/nonexistent', redirect[1]
1385
+ assert_match('/nonexistent', redirect[1])
700
1386
  end
701
1387
 
702
1388
  def test_head_accessor
@@ -707,7 +1393,7 @@ class TestCurbCurlEasy < Test::Unit::TestCase
707
1393
  redirect = curl.header_str.match(/Location: (.*)/)
708
1394
 
709
1395
  assert_equal '', curl.body_str
710
- assert_match '/nonexistent', redirect[1]
1396
+ assert_match('/nonexistent', redirect[1])
711
1397
  curl.head = false
712
1398
  curl.perform
713
1399
  assert_equal 'GET', curl.body_str
@@ -717,9 +1403,11 @@ class TestCurbCurlEasy < Test::Unit::TestCase
717
1403
  curl = Curl::Easy.new(TestServlet.url)
718
1404
  curl.headers['Content-Type'] = 'application/json'
719
1405
  assert curl.http_put("message")
720
- assert_match /^PUT/, curl.body_str
721
- assert_match /message$/, curl.body_str
722
- assert_match /application\/json/, curl.header_str
1406
+ assert_match(/^PUT/, curl.body_str)
1407
+ assert_match(/message$/, curl.body_str)
1408
+ assert_match(/message$/, curl.body)
1409
+ assert_match(/application\/json/, curl.header_str)
1410
+ assert_match(/application\/json/, curl.head)
723
1411
  end
724
1412
 
725
1413
  def test_put_data
@@ -728,8 +1416,46 @@ class TestCurbCurlEasy < Test::Unit::TestCase
728
1416
 
729
1417
  curl.perform
730
1418
 
731
- assert_match /^PUT/, curl.body_str
732
- assert_match /message$/, curl.body_str
1419
+ assert_match(/^PUT/, curl.body_str)
1420
+ assert_match(/message$/, curl.body_str)
1421
+ end
1422
+
1423
+ def test_put_data_from_to_s_object
1424
+ curl = Curl::Easy.new(TestServlet.url)
1425
+ curl.put_data = :message
1426
+
1427
+ curl.perform
1428
+
1429
+ assert_equal "PUT\nmessage", curl.body_str
1430
+ end
1431
+
1432
+ def test_put_data_read_exception_sets_result_and_releases_callback_state
1433
+ error_class = Class.new(StandardError)
1434
+ reader_class = Class.new do
1435
+ define_method(:read) do |_len|
1436
+ raise error_class, 'upload read failed'
1437
+ end
1438
+
1439
+ def seek(_offset, _whence = SEEK_SET)
1440
+ 0
1441
+ end
1442
+
1443
+ def stat
1444
+ Struct.new(:size).new(1)
1445
+ end
1446
+ end
1447
+
1448
+ curl = Curl::Easy.new(TestServlet.url)
1449
+ curl.put_data = reader_class.new
1450
+
1451
+ error = assert_raise(error_class) { curl.perform }
1452
+ assert_equal 'upload read failed', error.message
1453
+ assert_not_equal 0, curl.last_result
1454
+
1455
+ curl.url = TestServlet.url
1456
+ curl.http_get
1457
+
1458
+ assert_equal 'GET', curl.body_str
733
1459
  end
734
1460
 
735
1461
  # https://github.com/taf2/curb/issues/101
@@ -739,8 +1465,8 @@ class TestCurbCurlEasy < Test::Unit::TestCase
739
1465
 
740
1466
  curl.perform
741
1467
 
742
- assert_match /^PUT/, curl.body_str
743
- assert_match "a\0b", curl.body_str
1468
+ assert_match(/^PUT/, curl.body_str)
1469
+ assert_match("a\0b", curl.body_str)
744
1470
  end
745
1471
 
746
1472
  def test_put_nil_data_no_crash
@@ -755,7 +1481,7 @@ class TestCurbCurlEasy < Test::Unit::TestCase
755
1481
  File.open(__FILE__,'rb') do|f|
756
1482
  assert curl.http_put(f)
757
1483
  end
758
- assert_equal "PUT\n#{File.read(__FILE__)}", curl.body_str
1484
+ assert_equal "PUT\n#{File.read(__FILE__)}", curl.body_str.tr("\r", '')
759
1485
  end
760
1486
 
761
1487
  def test_put_class_method
@@ -765,7 +1491,7 @@ class TestCurbCurlEasy < Test::Unit::TestCase
765
1491
  assert_equal Curl::Easy, c.class
766
1492
  end
767
1493
  assert_equal 1, count
768
- assert_equal "PUT\n#{File.read(__FILE__)}", curl.body_str
1494
+ assert_equal "PUT\n#{File.read(__FILE__)}", curl.body_str.tr("\r", '')
769
1495
  end
770
1496
 
771
1497
  # Generate a self-signed cert with
@@ -774,7 +1500,7 @@ class TestCurbCurlEasy < Test::Unit::TestCase
774
1500
  def test_cert
775
1501
  curl = Curl::Easy.new(TestServlet.url)
776
1502
  curl.cert= File.join(File.dirname(__FILE__),"cert.pem")
777
- assert_match /cert.pem$/,curl.cert
1503
+ assert_match(/cert.pem$/,curl.cert)
778
1504
  end
779
1505
 
780
1506
  def test_cert_with_password
@@ -782,7 +1508,7 @@ class TestCurbCurlEasy < Test::Unit::TestCase
782
1508
  path = File.join(File.dirname(__FILE__),"cert.pem")
783
1509
  curl.certpassword = 'password'
784
1510
  curl.cert = path
785
- assert_match /cert.pem$/,curl.cert
1511
+ assert_match(/cert.pem$/,curl.cert)
786
1512
  end
787
1513
 
788
1514
  def test_cert_type
@@ -803,7 +1529,7 @@ class TestCurbCurlEasy < Test::Unit::TestCase
803
1529
  def test_ca_cert
804
1530
  curl = Curl::Easy.new(TestServlet.url)
805
1531
  curl.cacert= File.join(File.dirname(__FILE__),"cacert.pem")
806
- assert_match /cacert.pem$/, curl.cacert
1532
+ assert_match(/cacert.pem$/, curl.cacert)
807
1533
  end
808
1534
 
809
1535
  def test_user_agent
@@ -829,7 +1555,15 @@ class TestCurbCurlEasy < Test::Unit::TestCase
829
1555
  $auth_header = nil
830
1556
  # curl checks the auth type supported by the server, so we have to create a
831
1557
  # new easy handle if we're going to change the auth type...
832
-
1558
+ if WINDOWS
1559
+ # On Windows, libcurl often uses SSPI for NTLM which yields a different
1560
+ # header value and encoding; skip the NTLM-specific assertion.
1561
+ return
1562
+ end
1563
+ # curl 8.20.0 disables NTLM by default and plans to remove it in
1564
+ # September 2026; libcurl silently downgrades to Basic when NTLM is
1565
+ # unavailable, so skip the NTLM-specific assertion in that case.
1566
+ return unless Curl.ntlm?
833
1567
  curl = Curl::Easy.new(TestServlet.url)
834
1568
  curl.username = "foo"
835
1569
  curl.password = "bar"
@@ -850,7 +1584,7 @@ class TestCurbCurlEasy < Test::Unit::TestCase
850
1584
  end
851
1585
 
852
1586
  def test_post_streaming
853
- readme = File.expand_path(File.join(File.dirname(__FILE__),'..','README'))
1587
+ readme = File.expand_path(File.join(File.dirname(__FILE__),'..','README.md'))
854
1588
 
855
1589
  pf = Curl::PostField.file("filename", readme)
856
1590
 
@@ -860,11 +1594,10 @@ class TestCurbCurlEasy < Test::Unit::TestCase
860
1594
  easy.multipart_form_post = true
861
1595
  easy.http_post(pf)
862
1596
 
863
- assert_not_equal(0,easy.body_str.size)
864
- assert_equal(easy.body_str,File.read(readme))
1597
+ assert_not_equal(0,easy.body.size)
1598
+ assert_equal(Digest::MD5.hexdigest(easy.body), Digest::MD5.hexdigest(File.binread(readme)))
865
1599
  end
866
1600
 
867
-
868
1601
  def test_easy_close
869
1602
  easy = Curl::Easy.new
870
1603
  easy.close
@@ -872,6 +1605,18 @@ class TestCurbCurlEasy < Test::Unit::TestCase
872
1605
  easy.http_get
873
1606
  end
874
1607
 
1608
+ def test_easy_close_restores_error_buffer
1609
+ easy = Curl::Easy.new('http://127.0.0.1:1')
1610
+ assert_raise(Curl::Err::ConnectionFailedError) { easy.perform }
1611
+ assert_not_nil easy.last_error
1612
+
1613
+ easy.close
1614
+ easy.url = 'http://127.0.0.1:1'
1615
+ assert_raise(Curl::Err::ConnectionFailedError) { easy.perform }
1616
+
1617
+ assert_not_nil easy.last_error
1618
+ end
1619
+
875
1620
  def test_easy_reset
876
1621
  easy = Curl::Easy.new
877
1622
  easy.url = TestServlet.url + "?query=foo"
@@ -884,6 +1629,18 @@ class TestCurbCurlEasy < Test::Unit::TestCase
884
1629
  easy.http_get
885
1630
  end
886
1631
 
1632
+ def test_last_result_initialization
1633
+ # Test for issue #463 - ensure last_result is properly initialized to 0
1634
+ easy = Curl::Easy.new
1635
+ assert_equal 0, easy.last_result, "last_result should be initialized to 0 for new instance"
1636
+
1637
+ # Test that reset also sets last_result to 0
1638
+ easy.url = TestServlet.url
1639
+ easy.http_get
1640
+ easy.reset
1641
+ assert_equal 0, easy.last_result, "last_result should be reset to 0 after calling reset"
1642
+ end
1643
+
887
1644
  def test_easy_use_http_versions
888
1645
  easy = Curl::Easy.new
889
1646
  easy.url = TestServlet.url + "?query=foo"
@@ -909,16 +1666,22 @@ class TestCurbCurlEasy < Test::Unit::TestCase
909
1666
  assert_equal "PUT\nhello", curl.body_str
910
1667
  curl.http('COPY')
911
1668
  assert_equal 'COPY', curl.body_str
1669
+
1670
+ curl.http_patch
1671
+ assert_equal "PATCH\n", curl.body_str
1672
+
1673
+ curl.http_put
1674
+ assert_equal "PUT\n", curl.body_str
912
1675
  end
913
1676
 
914
1677
  def test_easy_http_verbs_must_respond_to_str
915
- # issue http://github.com/taf2/curb/issues#issue/45
1678
+ # issue http://github.com/taf2/curb/issues/45
916
1679
  assert_nothing_raised do
917
- c = Curl::Easy.new ; c.url = 'http://example.com' ; c.http(:get)
1680
+ c = Curl::Easy.new ; c.url = TestServlet.url ; c.http(:get)
918
1681
  end
919
1682
 
920
1683
  assert_raise RuntimeError do
921
- c = Curl::Easy.new ; c.url = 'http://example.com' ; c.http(FooNoToS.new)
1684
+ c = Curl::Easy.new ; c.url = TestServlet.url ; c.http(FooNoToS.new)
922
1685
  end
923
1686
 
924
1687
  end
@@ -957,8 +1720,8 @@ class TestCurbCurlEasy < Test::Unit::TestCase
957
1720
  curl.headers['Accept'] = '*/*'
958
1721
  curl.headers['Authorization'] = 'Foo Bar Biz Baz'
959
1722
  curl.http_put(rd)
960
- assert_match /^PUT/, curl.body_str
961
- assert_match /hello$/, curl.body_str
1723
+ assert_match(/^PUT/, curl.body_str)
1724
+ assert_match(/hello$/, curl.body_str)
962
1725
  curl.header_str
963
1726
  curl.body_str
964
1727
  end
@@ -991,7 +1754,7 @@ class TestCurbCurlEasy < Test::Unit::TestCase
991
1754
  c = Curl::Easy.new($TEST_URL)
992
1755
  c.on_success {|x| raise "error" }
993
1756
  c.perform
994
- rescue => e
1757
+ rescue Curl::Err::AbortedByCallbackError => e
995
1758
  assert_equal 'Curl::Err::AbortedByCallbackError', e.class.to_s
996
1759
  c.close
997
1760
  end
@@ -1017,6 +1780,91 @@ class TestCurbCurlEasy < Test::Unit::TestCase
1017
1780
  end
1018
1781
  end
1019
1782
 
1783
+ def test_close_in_on_progress_is_blocked
1784
+ curl = Curl::Easy.new(TestServlet.url)
1785
+ did_raise = false
1786
+
1787
+ curl.on_progress do |_dltotal, _dlnow, _ultotal, _ulnow|
1788
+ unless did_raise
1789
+ begin
1790
+ curl.close
1791
+ rescue RuntimeError
1792
+ did_raise = true
1793
+ end
1794
+ end
1795
+ true
1796
+ end
1797
+
1798
+ curl.perform
1799
+ assert did_raise
1800
+ end
1801
+
1802
+ def test_close_in_on_debug_is_blocked
1803
+ curl = Curl::Easy.new(TestServlet.url)
1804
+ did_raise = false
1805
+
1806
+ curl.on_debug do |_type, _data|
1807
+ unless did_raise
1808
+ begin
1809
+ curl.close
1810
+ rescue RuntimeError
1811
+ did_raise = true
1812
+ end
1813
+ end
1814
+ end
1815
+
1816
+ curl.perform
1817
+ assert did_raise
1818
+ end
1819
+
1820
+ def test_close_in_upload_read_is_blocked
1821
+ curl = Curl::Easy.new(TestServlet.url)
1822
+
1823
+ reader_class = Class.new do
1824
+ attr_reader :close_blocked
1825
+
1826
+ def initialize(curl)
1827
+ @curl = curl
1828
+ @done = false
1829
+ @close_blocked = false
1830
+ end
1831
+
1832
+ def read(_len)
1833
+ return nil if @done
1834
+
1835
+ @done = true
1836
+ begin
1837
+ @curl.close
1838
+ rescue RuntimeError
1839
+ @close_blocked = true
1840
+ end
1841
+
1842
+ 'hello'
1843
+ end
1844
+
1845
+ def seek(_offset, _whence = SEEK_SET)
1846
+ 0
1847
+ end
1848
+
1849
+ def stat
1850
+ Struct.new(:size).new(5)
1851
+ end
1852
+ end
1853
+
1854
+ reader = reader_class.new(curl)
1855
+ curl.http_put(reader)
1856
+
1857
+ assert reader.close_blocked
1858
+ assert_equal "PUT\nhello", curl.body_str
1859
+ end
1860
+
1861
+ def test_set_unsupported_options
1862
+ curl = Curl::Easy.new
1863
+ assert_raises TypeError do
1864
+ curl.set(99999, 1)
1865
+ end
1866
+ end
1867
+
1020
1868
  include TestServerMethods
1021
1869
 
1022
1870
  def setup