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
@@ -0,0 +1,237 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'fileutils'
5
+ require 'objspace'
6
+ require 'optparse'
7
+ require 'rbconfig'
8
+ require 'tmpdir'
9
+
10
+ TOPDIR = File.expand_path('..', __dir__)
11
+ LIBDIR = File.join(TOPDIR, 'lib')
12
+ EXTDIR = File.join(TOPDIR, 'ext')
13
+ $LOAD_PATH.unshift(LIBDIR)
14
+ $LOAD_PATH.unshift(EXTDIR)
15
+
16
+ require 'curb'
17
+
18
+ module LeakTrace
19
+ Record = Struct.new(:identifier, :created_location, :closed, keyword_init: true)
20
+
21
+ module_function
22
+
23
+ def install_multi_trace!
24
+ return if @multi_trace_installed
25
+
26
+ @multi_trace_installed = true
27
+ @multi_records = {}
28
+
29
+ multi_singleton = class << Curl::Multi
30
+ self
31
+ end
32
+
33
+ multi_singleton.alias_method(:__leak_trace_new, :new)
34
+ multi_singleton.define_method(:new) do |*args, **kwargs, &block|
35
+ multi = if kwargs.empty?
36
+ __leak_trace_new(*args, &block)
37
+ else
38
+ __leak_trace_new(*args, **kwargs, &block)
39
+ end
40
+
41
+ LeakTrace.multi_records[multi.object_id] ||= Record.new(
42
+ identifier: multi.object_id,
43
+ created_location: caller_locations(1, 6).map(&:to_s),
44
+ closed: false
45
+ )
46
+ multi
47
+ end
48
+
49
+ Curl::Multi.class_eval do
50
+ alias_method :__leak_trace__close, :_close
51
+
52
+ def _close(*args, **kwargs, &block)
53
+ record = LeakTrace.multi_records[object_id]
54
+ record.closed = true if record
55
+
56
+ if kwargs.empty?
57
+ __leak_trace__close(*args, &block)
58
+ else
59
+ __leak_trace__close(*args, **kwargs, &block)
60
+ end
61
+ end
62
+ end
63
+ end
64
+
65
+ def multi_records
66
+ @multi_records ||= {}
67
+ end
68
+
69
+ def live_multis
70
+ ObjectSpace.each_object(Curl::Multi).to_a
71
+ end
72
+
73
+ def fixture_path
74
+ File.expand_path('helper.rb', __dir__)
75
+ end
76
+
77
+ def fixture_url
78
+ path = fixture_path.tr('\\', '/')
79
+ "file://#{path.start_with?('/') ? '' : '/'}#{path}"
80
+ end
81
+
82
+ def full_gc(compact: false)
83
+ GC.start(full_mark: true, immediate_sweep: true)
84
+ GC.compact if compact && GC.respond_to?(:compact)
85
+ GC.start(full_mark: true, immediate_sweep: true)
86
+ end
87
+
88
+ def close_multi(multi)
89
+ return unless multi
90
+
91
+ multi.instance_variable_set(:@requests, {}) if multi.respond_to?(:instance_variable_set)
92
+ multi._close
93
+ rescue StandardError
94
+ multi.close rescue nil
95
+ end
96
+
97
+ def multi_perform(iterations:, handles:, close:, compact:)
98
+ iterations.times do
99
+ multi = Curl::Multi.new
100
+ handles.times { multi.add(Curl::Easy.new(fixture_url)) }
101
+ multi.perform
102
+ ensure
103
+ close_multi(multi) if close
104
+ multi = nil
105
+ full_gc(compact: compact)
106
+ end
107
+ end
108
+
109
+ def multi_gc_cleanup(iterations:, handles:, compact:)
110
+ iterations.times do
111
+ multi = Curl::Multi.new
112
+ handles.times { multi.add(Curl::Easy.new(fixture_url)) }
113
+ multi.perform
114
+ multi = nil
115
+ full_gc(compact: compact)
116
+ end
117
+ end
118
+
119
+ def easy_perform(iterations:, compact:)
120
+ iterations.times do
121
+ easy = Curl::Easy.new(fixture_url)
122
+ easy.perform
123
+ easy.close
124
+ easy = nil
125
+ full_gc(compact: compact)
126
+ end
127
+ end
128
+
129
+ def download(iterations:, compact:)
130
+ iterations.times do |i|
131
+ Dir.mktmpdir("curb-leak-trace-#{i}") do |dir|
132
+ Curl::Easy.download(fixture_url, File.join(dir, 'helper-copy.rb'))
133
+ end
134
+ full_gc(compact: compact)
135
+ end
136
+ end
137
+
138
+ def report(io:, verbose:)
139
+ live = live_multis
140
+ tracked_live = live.filter_map { |multi| multi_records[multi.object_id] }
141
+
142
+ io.puts "scanned live Curl::Multi objects: #{live.size}"
143
+ io.puts "tracked Curl::Multi allocations: #{multi_records.size}"
144
+ io.puts "tracked Curl::Multi closes: #{multi_records.count { |_id, record| record.closed }}"
145
+
146
+ grouped = tracked_live.group_by { |record| record.created_location.first || '<unknown>' }
147
+ grouped.sort_by { |location, records| [-records.size, location] }.each do |location, records|
148
+ io.puts "live #{records.size}: #{location}"
149
+ next unless verbose
150
+
151
+ records.each do |record|
152
+ io.puts " object_id=#{record.identifier}"
153
+ record.created_location.drop(1).each do |line|
154
+ io.puts " #{line}"
155
+ end
156
+ end
157
+ end
158
+ end
159
+ end
160
+
161
+ options = {
162
+ scenario: 'multi_perform',
163
+ iterations: 25,
164
+ handles: 3,
165
+ close: true,
166
+ compact: false,
167
+ verbose: false,
168
+ fail_on_live: false
169
+ }
170
+
171
+ OptionParser.new do |opts|
172
+ opts.banner = 'Usage: ruby tests/leak_trace.rb [options]'
173
+
174
+ opts.on('--scenario NAME', 'multi_perform, multi_gc_cleanup, easy_perform, download') do |value|
175
+ options[:scenario] = value
176
+ end
177
+
178
+ opts.on('--iterations N', Integer, 'number of iterations to run') do |value|
179
+ options[:iterations] = value
180
+ end
181
+
182
+ opts.on('--handles N', Integer, 'number of easy handles per multi iteration') do |value|
183
+ options[:handles] = value
184
+ end
185
+
186
+ opts.on('--[no-]close', 'explicitly close multi handles when the scenario finishes') do |value|
187
+ options[:close] = value
188
+ end
189
+
190
+ opts.on('--compact', 'run GC.compact between iterations when available') do
191
+ options[:compact] = true
192
+ end
193
+
194
+ opts.on('--verbose', 'print full creation stacks for live multi handles') do
195
+ options[:verbose] = true
196
+ end
197
+
198
+ opts.on('--fail-on-live', 'exit non-zero when any live Curl::Multi objects remain') do
199
+ options[:fail_on_live] = true
200
+ end
201
+ end.parse!
202
+
203
+ LeakTrace.install_multi_trace!
204
+
205
+ case options[:scenario]
206
+ when 'multi_perform'
207
+ LeakTrace.multi_perform(
208
+ iterations: options[:iterations],
209
+ handles: options[:handles],
210
+ close: options[:close],
211
+ compact: options[:compact]
212
+ )
213
+ when 'multi_gc_cleanup'
214
+ LeakTrace.multi_gc_cleanup(
215
+ iterations: options[:iterations],
216
+ handles: options[:handles],
217
+ compact: options[:compact]
218
+ )
219
+ when 'easy_perform'
220
+ LeakTrace.easy_perform(
221
+ iterations: options[:iterations],
222
+ compact: options[:compact]
223
+ )
224
+ when 'download'
225
+ LeakTrace.download(
226
+ iterations: options[:iterations],
227
+ compact: options[:compact]
228
+ )
229
+ else
230
+ warn "unknown scenario: #{options[:scenario]}"
231
+ exit 64
232
+ end
233
+
234
+ LeakTrace.full_gc(compact: options[:compact])
235
+ LeakTrace.report(io: $stdout, verbose: options[:verbose])
236
+
237
+ exit 1 if options[:fail_on_live] && LeakTrace.live_multis.any?
data/tests/mem_check.rb CHANGED
@@ -6,6 +6,9 @@ require File.expand_path(File.join(File.dirname(__FILE__), 'helper'))
6
6
  # Run some tests to measure the memory usage of curb, these tests require fork and ps
7
7
  #
8
8
  class TestCurbMemory < Test::Unit::TestCase
9
+ def setup
10
+ omit('Memory fork/ps tests not supported on Windows') if WINDOWS || NO_FORK
11
+ end
9
12
 
10
13
  def test_easy_memory
11
14
  easy_avg, easy_std = measure_object_memory(Curl::Easy)
data/tests/tc_curl.rb CHANGED
@@ -31,7 +31,37 @@ class TestCurl < Test::Unit::TestCase
31
31
  assert_equal "OPTIONSfoo=bar", curl.body_str
32
32
  end
33
33
 
34
- include TestServerMethods
34
+ def test_urlalize_without_extra_params
35
+ url_no_params = 'http://localhost/test'
36
+ url_with_params = 'http://localhost/test?a=1'
37
+
38
+ assert_equal(url_no_params, Curl.urlalize(url_no_params))
39
+ assert_equal(url_with_params, Curl.urlalize(url_with_params))
40
+ end
41
+
42
+ def test_urlalize_with_nil_as_params
43
+ url = 'http://localhost/test'
44
+ assert_equal(url, Curl.urlalize(url, nil))
45
+ end
46
+
47
+ def test_urlalize_with_extra_params
48
+ url_no_params = 'http://localhost/test'
49
+ url_with_params = 'http://localhost/test?a=1'
50
+ extra_params = { :b => 2 }
51
+
52
+ expected_url_no_params = 'http://localhost/test?b=2'
53
+ expected_url_with_params = 'http://localhost/test?a=1&b=2'
54
+
55
+ assert_equal(expected_url_no_params, Curl.urlalize(url_no_params, extra_params))
56
+ assert_equal(expected_url_with_params, Curl.urlalize(url_with_params, extra_params))
57
+ end
58
+
59
+ def test_urlalize_does_not_strip_trailing_?
60
+ url_empty_params = 'http://localhost/test?'
61
+ assert_equal(url_empty_params, Curl.urlalize(url_empty_params))
62
+ end
63
+
64
+ include TestServerMethods
35
65
 
36
66
  def setup
37
67
  server_setup
@@ -6,12 +6,12 @@ class TestCurbCurlDownload < Test::Unit::TestCase
6
6
  def setup
7
7
  server_setup
8
8
  end
9
-
9
+
10
10
  def test_download_url_to_file_via_string
11
11
  dl_url = "http://127.0.0.1:9129/ext/curb_easy.c"
12
12
  dl_path = File.join(Dir::tmpdir, "dl_url_test.file")
13
13
 
14
- curb = Curl::Easy.download(dl_url, dl_path)
14
+ Curl::Easy.download(dl_url, dl_path)
15
15
  assert File.exist?(dl_path)
16
16
  assert_equal File.read(File.join(File.dirname(__FILE__), '..','ext','curb_easy.c')), File.read(dl_path)
17
17
  ensure
@@ -23,7 +23,7 @@ class TestCurbCurlDownload < Test::Unit::TestCase
23
23
  dl_path = File.join(Dir::tmpdir, "dl_url_test.file")
24
24
  io = File.open(dl_path, 'wb')
25
25
 
26
- curb = Curl::Easy.download(dl_url, io)
26
+ Curl::Easy.download(dl_url, io)
27
27
  assert io.closed?
28
28
  assert File.exist?(dl_path)
29
29
  assert_equal File.read(File.join(File.dirname(__FILE__), '..','ext','curb_easy.c')), File.read(dl_path)
@@ -32,15 +32,19 @@ class TestCurbCurlDownload < Test::Unit::TestCase
32
32
  end
33
33
 
34
34
  def test_download_url_to_file_via_io
35
+ omit('fork not available on this platform') if NO_FORK || WINDOWS
35
36
  dl_url = "http://127.0.0.1:9129/ext/curb_easy.c"
36
37
  dl_path = File.join(Dir::tmpdir, "dl_url_test.file")
37
38
  reader, writer = IO.pipe
38
39
 
39
40
  # Write to local file
40
- fork do
41
+ child_pid = fork do
41
42
  begin
42
43
  writer.close
43
44
  File.open(dl_path, 'wb') { |file| file << reader.read }
45
+ exit! 0
46
+ rescue StandardError
47
+ exit! 1
44
48
  ensure
45
49
  reader.close rescue IOError # if the stream has already been closed
46
50
  end
@@ -49,8 +53,9 @@ class TestCurbCurlDownload < Test::Unit::TestCase
49
53
  # Download remote source
50
54
  begin
51
55
  reader.close
52
- curb = Curl::Easy.download(dl_url, writer)
53
- Process.wait
56
+ Curl::Easy.download(dl_url, writer)
57
+ _pid, status = Process.wait2(child_pid)
58
+ assert_predicate status, :success?
54
59
  ensure
55
60
  writer.close rescue IOError # if the stream has already been closed, which occurs in Easy::download
56
61
  end
@@ -58,7 +63,7 @@ class TestCurbCurlDownload < Test::Unit::TestCase
58
63
  assert File.exist?(dl_path)
59
64
  assert_equal File.read(File.join(File.dirname(__FILE__), '..','ext','curb_easy.c')), File.read(dl_path)
60
65
  ensure
61
- File.unlink(dl_path) if File.exist?(dl_path)
66
+ File.unlink(dl_path) if dl_path && File.exist?(dl_path)
62
67
  end
63
68
 
64
69
  def test_download_bad_url_gives_404