curb 1.1.0 → 1.2.1

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.
data/ext/curb_multi.h CHANGED
@@ -8,18 +8,21 @@
8
8
  #define __CURB_MULTI_H
9
9
 
10
10
  #include "curb.h"
11
- #include "curb_easy.h"
12
11
  #include <curl/multi.h>
13
12
 
13
+ struct st_table;
14
+
14
15
  typedef struct {
15
16
  int active;
16
17
  int running;
17
18
  CURLM *handle;
19
+ struct st_table *attached;
18
20
  } ruby_curl_multi;
19
21
 
20
22
  extern VALUE cCurlMulti;
21
23
  void init_curb_multi();
22
24
  VALUE ruby_curl_multi_new(VALUE klass);
25
+ void rb_curl_multi_forget_easy(ruby_curl_multi *rbcm, void *rbce_ptr);
23
26
 
24
27
 
25
28
  #endif
data/ext/curb_postfield.c CHANGED
@@ -209,6 +209,9 @@ void curl_postfield_free(ruby_curl_postfield *rbcpf) {
209
209
  */
210
210
  static VALUE ruby_curl_postfield_new_content(int argc, VALUE *argv, VALUE klass) {
211
211
  ruby_curl_postfield *rbcpf = ALLOC(ruby_curl_postfield);
212
+ if (!rbcpf) {
213
+ rb_raise(rb_eNoMemError, "Failed to allocate memory for Curl::PostField");
214
+ }
212
215
 
213
216
  // wierdness - we actually require two args, unless a block is provided, but
214
217
  // we have to work that out below.
@@ -255,6 +258,9 @@ static VALUE ruby_curl_postfield_new_content(int argc, VALUE *argv, VALUE klass)
255
258
  static VALUE ruby_curl_postfield_new_file(int argc, VALUE *argv, VALUE klass) {
256
259
  // TODO needs to handle content-type too
257
260
  ruby_curl_postfield *rbcpf = ALLOC(ruby_curl_postfield);
261
+ if (!rbcpf) {
262
+ rb_raise(rb_eNoMemError, "Failed to allocate memory for Curl::PostField");
263
+ }
258
264
 
259
265
  rb_scan_args(argc, argv, "21&", &rbcpf->name, &rbcpf->local_file, &rbcpf->remote_file, &rbcpf->content_proc);
260
266
 
data/ext/curb_upload.c CHANGED
@@ -24,6 +24,9 @@ static void curl_upload_free(ruby_curl_upload *rbcu) {
24
24
  VALUE ruby_curl_upload_new(VALUE klass) {
25
25
  VALUE upload;
26
26
  ruby_curl_upload *rbcu = ALLOC(ruby_curl_upload);
27
+ if (!rbcu) {
28
+ rb_raise(rb_eNoMemError, "Failed to allocate memory for Curl::Upload");
29
+ }
27
30
  rbcu->stream = Qnil;
28
31
  rbcu->offset = 0;
29
32
  upload = Data_Wrap_Struct(klass, curl_upload_mark, curl_upload_free, rbcu);
data/ext/extconf.rb CHANGED
@@ -1,4 +1,10 @@
1
1
  require 'mkmf'
2
+ require 'tmpdir'
3
+ begin
4
+ require 'etc'
5
+ rescue LoadError
6
+ # Etc may not be available on all Ruby builds (very rare). Fallback later.
7
+ end
2
8
 
3
9
  dir_config('curl')
4
10
 
@@ -37,26 +43,195 @@ end
37
43
  # puts "Selected arch: #{archs.first}"
38
44
  #end
39
45
 
40
- def define(s, v=1)
41
- $defs.push( format("-D HAVE_%s=%d", s.to_s.upcase, v) )
46
+ def define(s, v = 1)
47
+ $defs.push(format("-D HAVE_%s=%d", s.to_s.upcase, v))
42
48
  end
43
49
 
44
- def have_constant(name)
45
- sname = name.is_a?(Symbol) ? name.to_s : name.upcase
46
- checking_for name do
47
- src = %{
48
- #include <curl/curl.h>
49
- int main() {
50
- int test = (int)#{sname};
51
- return 0;
52
- }
50
+ # Optional parallelization support for constant checks only.
51
+ # Enable automatically when job hints are present (JOBS, BUNDLE_JOBS, MAKEFLAGS -jN),
52
+ # or explicitly via EXTCONF_JOBS/EXTCONF_PARALLEL.
53
+
54
+ def parse_jobs_from_makeflags(flags)
55
+ return nil if flags.nil? || flags.empty?
56
+ tokens = flags.to_s.split(/\s+/)
57
+ jobs = nil
58
+ tokens.each_with_index do |tok, i|
59
+ case tok
60
+ when /\A-j(\d+)\z/
61
+ jobs = $1.to_i
62
+ when '-j'
63
+ nxt = tokens[i + 1]
64
+ jobs = nxt.to_i if nxt && nxt =~ /\A\d+\z/
65
+ when /\A--jobs(?:=(\d+)|\s+(\d+))\z/
66
+ jobs = ($1 || $2).to_i
67
+ when /\Aj(\d+)\z/ # sometimes make condenses flags
68
+ jobs = $1.to_i
69
+ end
70
+ break if jobs && jobs > 0
71
+ end
72
+ jobs
73
+ end
74
+
75
+ def detect_job_hints
76
+ # Priority: explicit extconf hint, then common envs used by bundler/rubygems
77
+ [
78
+ ENV['EXTCONF_JOBS'],
79
+ ENV['JOBS'],
80
+ ENV['BUNDLE_JOBS'],
81
+ parse_jobs_from_makeflags(ENV['MAKEFLAGS'])
82
+ ].each do |v|
83
+ n = v.to_i if v
84
+ return n if n && n > 0
85
+ end
86
+ nil
87
+ end
88
+
89
+ explicit_parallel = ENV.key?('EXTCONF_PARALLEL') && ENV['EXTCONF_PARALLEL'] == '1'
90
+ job_hints = detect_job_hints
91
+
92
+ DEFAULT_PARALLEL_JOBS = begin
93
+ n = Etc.respond_to?(:nprocessors) ? Etc.nprocessors.to_i : 1
94
+ n = 1 if n <= 0
95
+ # Use half the CPUs by default (rounded up), but at least 1
96
+ jobs = [(n.to_f / 2).ceil, 1].max
97
+ jobs
98
+ rescue
99
+ 1
100
+ end
101
+
102
+ PARALLEL_JOBS = begin
103
+ if job_hints && job_hints > 0
104
+ job_hints
105
+ else
106
+ # If no hints provided, default to half the CPUs.
107
+ DEFAULT_PARALLEL_JOBS
108
+ end
109
+ end
110
+
111
+ # Only enable if the platform supports fork. On Windows this stays sequential.
112
+ PARALLEL_CONSTANT_CHECKS = PARALLEL_JOBS > 1 && Process.respond_to?(:fork)
113
+
114
+ $queued_constants = []
115
+
116
+ def try_constant_compile(sname)
117
+ src = %{
118
+ #include <curl/curl.h>
119
+ int main() {
120
+ int test = (int)#{sname};
121
+ (void)test;
122
+ return 0;
53
123
  }
54
- if try_compile(src,"#{$CFLAGS} #{$LIBS}")
55
- define name
56
- true
57
- else
58
- #define name, 0
59
- false
124
+ }
125
+ try_compile(src, "#{$CFLAGS} #{$LIBS}")
126
+ end
127
+
128
+ def have_constant(name)
129
+ # Queue constants for optional parallel probing. Falls back to sequential.
130
+ if PARALLEL_CONSTANT_CHECKS
131
+ $queued_constants << name
132
+ true
133
+ else
134
+ sname = name.is_a?(Symbol) ? name.to_s : name.upcase
135
+ checking_for name do
136
+ if try_constant_compile(sname)
137
+ define name
138
+ true
139
+ else
140
+ false
141
+ end
142
+ end
143
+ end
144
+ end
145
+
146
+ def flush_constant_checks
147
+ return if $queued_constants.empty?
148
+
149
+ constants = $queued_constants.uniq
150
+ $queued_constants.clear
151
+
152
+ # On platforms without fork (e.g., Windows), run sequentially.
153
+ unless PARALLEL_CONSTANT_CHECKS
154
+ constants.each { |name| have_constant(name) }
155
+ return
156
+ end
157
+
158
+ # Run constant probes in isolated child processes to avoid mkmf
159
+ # scratch file and logfile contention.
160
+ results = {}
161
+ pending = constants.dup
162
+ running = {}
163
+ max_jobs = PARALLEL_JOBS
164
+
165
+ spawn_probe = lambda do |const_name|
166
+ r, w = IO.pipe
167
+ pid = fork do
168
+ begin
169
+ r.close
170
+ Dir.mktmpdir('curb-mkmf-') do |dir|
171
+ Dir.chdir(dir) do
172
+ begin
173
+ # Avoid clobbering the main mkmf.log
174
+ Logging::logfile = File.open(File::NULL, 'w') rescue File.open(File.join(dir, 'mkmf.log'), 'w')
175
+ rescue
176
+ # best-effort
177
+ end
178
+ sname = const_name.is_a?(Symbol) ? const_name.to_s : const_name.upcase
179
+ ok = try_constant_compile(sname)
180
+ w.write([const_name, ok ? 1 : 0].join("\t"))
181
+ end
182
+ end
183
+ rescue
184
+ # Treat as failure if anything unexpected happens in child
185
+ begin
186
+ w.write([const_name, 0].join("\t"))
187
+ rescue
188
+ end
189
+ ensure
190
+ begin w.close rescue nil end
191
+ # Ensure the child exits without running at_exit handlers
192
+ exit! 0
193
+ end
194
+ end
195
+ w.close
196
+ running[pid] = r
197
+ end
198
+
199
+ # Start initial batch
200
+ while running.size < max_jobs && !pending.empty?
201
+ spawn_probe.call(pending.shift)
202
+ end
203
+
204
+ # Collect results and keep spawning until done
205
+ until running.empty?
206
+ pid = Process.wait
207
+ io = running.delete(pid)
208
+ if io
209
+ begin
210
+ msg = io.read.to_s
211
+ name_str, ok_str = msg.split("\t", 2)
212
+ if name_str
213
+ # Map back to the original object if symbol-like
214
+ original = constants.find { |n| n.to_s == name_str }
215
+ results[original || name_str] = ok_str.to_i == 1
216
+ end
217
+ ensure
218
+ begin io.close rescue nil end
219
+ end
220
+ end
221
+ # Fill next task slot
222
+ spawn_probe.call(pending.shift) unless pending.empty?
223
+ end
224
+
225
+ # Apply results to $defs and output summary via checking_for
226
+ results.each do |const_name, ok|
227
+ sname = const_name.is_a?(Symbol) ? const_name.to_s : const_name.upcase
228
+ checking_for const_name do
229
+ if ok
230
+ define const_name
231
+ true
232
+ else
233
+ false
234
+ end
60
235
  end
61
236
  end
62
237
  end
@@ -325,6 +500,7 @@ have_constant "curlusessl_try"
325
500
  have_constant "curlusessl_control"
326
501
  have_constant "curlusessl_all"
327
502
  have_constant "curlopt_resolve"
503
+ have_constant "curlopt_request_target"
328
504
  have_constant "curlopt_sslcert"
329
505
  have_constant "curlopt_sslcerttype"
330
506
  have_constant "curlopt_sslkey"
@@ -469,9 +645,31 @@ test_for("curl_easy_escape", "CURL_EASY_ESCAPE", %{
469
645
  have_func('rb_thread_blocking_region')
470
646
  have_header('ruby/thread.h') && have_func('rb_thread_call_without_gvl', 'ruby/thread.h')
471
647
  have_header('ruby/io.h')
648
+ have_func('rb_thread_fd_select', 'ruby/io.h')
649
+ have_func('rb_wait_for_single_fd', 'ruby/io.h')
650
+ have_header('ruby/fiber/scheduler.h')
651
+ have_func('rb_fiber_scheduler_current', 'ruby/fiber/scheduler.h')
652
+ have_func('rb_fiber_scheduler_io_wait', 'ruby/fiber/scheduler.h')
472
653
  have_func('rb_io_stdio_file')
473
654
  have_func('curl_multi_wait')
655
+ have_func('curl_multi_socket_action')
656
+ have_func('curl_multi_assign')
657
+ have_func('curl_multi_wakeup')
658
+ have_func('curl_multi_poll')
659
+ have_func('curl_multi_socket')
660
+ have_func('curl_multi_timer_callback')
661
+ have_constant 'curlmopt_socketfunction'
662
+ have_constant 'curlmopt_timerfunction'
474
663
  have_func('curl_easy_duphandle')
475
664
 
665
+ # Optional: enable verbose socket-action debug logging.
666
+ # Set CURB_SOCKET_DEBUG=1 in the environment before running extconf to enable.
667
+ if ENV['CURB_SOCKET_DEBUG'] == '1'
668
+ $defs << '-DCURB_SOCKET_DEBUG=1'
669
+ end
670
+
671
+ # Run any queued constant checks (in parallel if enabled) before header generation.
672
+ flush_constant_checks
673
+
476
674
  create_header('curb_config.h')
477
675
  create_makefile('curb_core')
data/lib/curl/easy.rb CHANGED
@@ -158,6 +158,22 @@ module Curl
158
158
  set :proxy, url
159
159
  end
160
160
 
161
+ #
162
+ # call-seq:
163
+ # easy.request_target = string => string
164
+ #
165
+ # Set the request-target used in the HTTP request line (libcurl CURLOPT_REQUEST_TARGET).
166
+ # Useful for absolute-form request targets (e.g., when speaking to proxies) or
167
+ # special targets like "*" (OPTIONS *). Requires libcurl with CURLOPT_REQUEST_TARGET support.
168
+ #
169
+ def request_target=(value)
170
+ if Curl.const_defined?(:CURLOPT_REQUEST_TARGET)
171
+ set :request_target, value
172
+ else
173
+ raise NotImplementedError, "CURLOPT_REQUEST_TARGET is not supported by this libcurl"
174
+ end
175
+ end
176
+
161
177
  def ssl_verify_host=(value)
162
178
  value = 1 if value.class == TrueClass
163
179
  value = 0 if value.class == FalseClass
@@ -216,9 +232,18 @@ module Curl
216
232
  # call-seq:
217
233
  # easy.cookies = "name1=content1; name2=content2;" => string
218
234
  #
219
- # Set cookies to be sent by this Curl::Easy instance. The format of the string should
220
- # be NAME=CONTENTS, where NAME is the cookie name and CONTENTS is what the cookie should contain.
221
- # Set multiple cookies in one string like this: "name1=content1; name2=content2;" etc.
235
+ # Set the manual Cookie request header for this Curl::Easy instance.
236
+ # The format of the string should be NAME=CONTENTS, where NAME is the cookie name and
237
+ # CONTENTS is what the cookie should contain. Set multiple cookies in one string like this:
238
+ # "name1=content1; name2=content2;".
239
+ #
240
+ # Notes:
241
+ # - This only affects the outgoing Cookie header (libcurl CURLOPT_COOKIE) and does NOT
242
+ # alter the internal libcurl cookie engine (which stores cookies from Set-Cookie).
243
+ # - To change cookies stored in the engine, use {#cookielist} / {#cookielist=} or
244
+ # {#set} with :cookielist.
245
+ # - To clear a previously set manual Cookie header, assign an empty string ('').
246
+ # Assigning +nil+ has no effect in current curb versions.
222
247
  #
223
248
  def cookies=(value)
224
249
  set :cookie, value
@@ -233,6 +258,8 @@ module Curl
233
258
  # *Note* that you must set enable_cookies true to enable the cookie
234
259
  # engine, or this option will be ignored.
235
260
  #
261
+ # Note: assigning +nil+ has no effect; pass a path string to use a cookie file.
262
+ #
236
263
  def cookiefile=(value)
237
264
  set :cookiefile, value
238
265
  end
@@ -241,16 +268,40 @@ module Curl
241
268
  # call-seq:
242
269
  # easy.cookiejar = string => string
243
270
  #
244
- # Set a cookiejar file to use for this Curl::Easy instance.
245
- # Cookies from the response will be written into this file.
271
+ # Set a cookiejar file to use for this Curl::Easy instance. Cookies from the response
272
+ # will be written into this file.
246
273
  #
247
274
  # *Note* that you must set enable_cookies true to enable the cookie
248
275
  # engine, or this option will be ignored.
249
276
  #
277
+ # Note: assigning +nil+ has no effect; pass a path string to persist cookies to a file.
278
+ #
250
279
  def cookiejar=(value)
251
280
  set :cookiejar, value
252
281
  end
253
282
 
283
+ #
284
+ # call-seq:
285
+ # easy.cookielist = string => string
286
+ #
287
+ # Modify cookies in libcurl's internal cookie engine (CURLOPT_COOKIELIST).
288
+ # Accepts a Set-Cookie style string, one or more lines in Netscape cookie file format,
289
+ # or one of the special commands: "ALL" (clear), "SESS" (remove session cookies),
290
+ # "FLUSH" (write to jar), "RELOAD" (reload from file).
291
+ #
292
+ # Examples:
293
+ # easy.cookielist = "Set-Cookie: session=42; Domain=example.com; Path=/;"
294
+ # easy.cookielist = [
295
+ # ['.example.com', 'TRUE', '/', 'FALSE', 0, 'c1', 'v1'].join("\t"),
296
+ # ['.example.com', 'TRUE', '/', 'FALSE', 0, 'c2', 'v2'].join("\t"),
297
+ # ''
298
+ # ].join("\n")
299
+ # easy.cookielist = 'ALL' # clear all cookies in the engine
300
+ #
301
+ def cookielist=(value)
302
+ set :cookielist, value
303
+ end
304
+
254
305
  #
255
306
  # call-seq:
256
307
  # easy = Curl::Easy.new("url") do|c|
@@ -0,0 +1,56 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), 'helper'))
2
+
3
+ class BugIssueNoproxy < Test::Unit::TestCase
4
+ def test_noproxy_option_support
5
+ # Test that CURLOPT_NOPROXY constant is defined
6
+ assert Curl.const_defined?(:CURLOPT_NOPROXY), "CURLOPT_NOPROXY constant should be defined"
7
+
8
+ # Test basic noproxy setting using setopt
9
+ c = Curl::Easy.new('https://google.com')
10
+
11
+ # This should not raise an error
12
+ assert_nothing_raised do
13
+ c.setopt(Curl::CURLOPT_NOPROXY, "localhost,127.0.0.1")
14
+ end
15
+
16
+ # Test using the convenience method if it exists
17
+ if c.respond_to?(:noproxy=)
18
+ assert_nothing_raised do
19
+ c.noproxy = "localhost,127.0.0.1"
20
+ end
21
+
22
+ # Test getter if it exists
23
+ if c.respond_to?(:noproxy)
24
+ assert_equal "localhost,127.0.0.1", c.noproxy
25
+ end
26
+ end
27
+ end
28
+
29
+ def test_noproxy_with_set_method
30
+ c = Curl::Easy.new('https://google.com')
31
+
32
+ # The issue specifically mentions using the set method
33
+ # This currently raises TypeError as reported in the issue
34
+ assert_nothing_raised(TypeError) do
35
+ c.set(:noproxy, "localhost,127.0.0.1")
36
+ end
37
+ end
38
+
39
+ def test_noproxy_empty_string
40
+ c = Curl::Easy.new('https://google.com')
41
+
42
+ # Test setting empty string to override environment variables
43
+ assert_nothing_raised do
44
+ c.setopt(Curl::CURLOPT_NOPROXY, "")
45
+ end
46
+ end
47
+
48
+ def test_noproxy_nil_value
49
+ c = Curl::Easy.new('https://google.com')
50
+
51
+ # Test setting nil to reset
52
+ assert_nothing_raised do
53
+ c.setopt(Curl::CURLOPT_NOPROXY, nil)
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,93 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), 'helper'))
2
+ require 'json'
3
+
4
+ class BugIssuePostRedirect < Test::Unit::TestCase
5
+ include BugTestServerSetupTeardown
6
+
7
+ def setup
8
+ @port = 9998
9
+ super
10
+ # Mount a POST endpoint that returns a redirect
11
+ @server.mount_proc("/post_redirect") do |req, res|
12
+ if req.request_method == "POST"
13
+ res.status = 302
14
+ res['Location'] = "http://127.0.0.1:#{@port}/redirected"
15
+ res.body = "Redirecting..."
16
+ else
17
+ res.status = 405
18
+ res.body = "Method not allowed"
19
+ end
20
+ end
21
+
22
+ # Mount the redirect target
23
+ @server.mount_proc("/redirected") do |req, res|
24
+ res.status = 200
25
+ res['Content-Type'] = "text/plain"
26
+ res.body = "You have been redirected"
27
+ end
28
+ end
29
+
30
+ def test_post_with_max_redirects_zero_should_not_follow_redirect
31
+ # Test case replicating the issue: POST with max_redirects=0 and follow_location=false
32
+ # should NOT trigger on_redirect callback or follow the redirect
33
+
34
+ redirect_called = false
35
+
36
+ handle = Curl::Easy.new("http://127.0.0.1:#{@port}/post_redirect") do |curl|
37
+ curl.max_redirects = 0
38
+ curl.follow_location = false
39
+ curl.on_redirect do |easy|
40
+ redirect_called = true
41
+ end
42
+ curl.headers['Content-Type'] = 'application/json'
43
+ curl.post_body = {test: "data"}.to_json
44
+ end
45
+
46
+ handle.http(:POST)
47
+
48
+ # The response should be the redirect response (302)
49
+ assert_equal 302, handle.response_code
50
+ assert_match(/Redirecting/, handle.body_str)
51
+
52
+ # on_redirect should NOT be called when follow_location is false
53
+ assert !redirect_called, "on_redirect callback should not be called when follow_location is false"
54
+ end
55
+
56
+ def test_post_with_follow_location_true_triggers_redirect
57
+ # Test that on_redirect IS called when follow_location is true
58
+ redirect_called = false
59
+
60
+ handle = Curl::Easy.new("http://127.0.0.1:#{@port}/post_redirect") do |curl|
61
+ curl.follow_location = true
62
+ curl.on_redirect do |easy|
63
+ redirect_called = true
64
+ end
65
+ curl.headers['Content-Type'] = 'application/json'
66
+ curl.post_body = {test: "data"}.to_json
67
+ end
68
+
69
+ handle.http(:POST)
70
+
71
+ # Should follow the redirect and get the final response
72
+ assert_equal 200, handle.response_code
73
+ assert_match(/You have been redirected/, handle.body_str)
74
+
75
+ # on_redirect SHOULD be called when follow_location is true
76
+ assert redirect_called, "on_redirect callback should be called when follow_location is true"
77
+ end
78
+
79
+ def test_curl_post_class_method_respects_redirect_settings
80
+ # Test that Curl.post (class method) respects redirect settings
81
+ # According to the issue, this works correctly
82
+
83
+ response = Curl.post("http://127.0.0.1:#{@port}/post_redirect", {test: "data"}.to_json) do |curl|
84
+ curl.max_redirects = 0
85
+ curl.follow_location = false
86
+ curl.headers['Content-Type'] = 'application/json'
87
+ end
88
+
89
+ # Should get the redirect response, not follow it
90
+ assert_equal 302, response.response_code
91
+ assert_match(/Redirecting/, response.body_str)
92
+ end
93
+ end
@@ -0,0 +1,41 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), 'helper'))
2
+
3
+ class BugIssueSpnego < Test::Unit::TestCase
4
+ def test_spnego_detection_works
5
+ # The fix for issue #227 ensures that Curl.spnego? checks for both
6
+ # CURL_VERSION_SPNEGO (newer libcurl) and CURL_VERSION_GSSNEGOTIATE (older libcurl)
7
+ #
8
+ # We can't test that it returns true because that depends on how libcurl
9
+ # was compiled on the system. We can only test that:
10
+ # 1. The method exists and works
11
+ # 2. If CURLAUTH_GSSNEGOTIATE is available, we can use it
12
+
13
+ # Check if CURLAUTH_GSSNEGOTIATE is available
14
+ if Curl.const_defined?(:CURLAUTH_GSSNEGOTIATE) && Curl.const_get(:CURLAUTH_GSSNEGOTIATE) != 0
15
+ # Test that we can use GSSNEGOTIATE auth type
16
+ c = Curl::Easy.new('http://example.com')
17
+ assert_nothing_raised do
18
+ c.http_auth_types = Curl::CURLAUTH_GSSNEGOTIATE
19
+ end
20
+
21
+ # The fix ensures spnego? won't incorrectly return false when
22
+ # older libcurl versions have GSSNEGOTIATE support
23
+ # (The actual return value depends on system libcurl compilation)
24
+ end
25
+
26
+ # The important fix is that the method now checks both constants
27
+ # This test passes if no exceptions are raised
28
+ assert true, "SPNEGO detection is working correctly"
29
+ end
30
+
31
+ def test_spnego_method_exists
32
+ # The method should always exist
33
+ assert Curl.respond_to?(:spnego?), "Curl should respond to spnego?"
34
+ end
35
+
36
+ def test_spnego_returns_boolean
37
+ # The method should return a boolean
38
+ result = Curl.spnego?
39
+ assert [true, false].include?(result), "Curl.spnego? should return true or false, got #{result.inspect}"
40
+ end
41
+ end
data/tests/helper.rb CHANGED
@@ -11,6 +11,34 @@ $LIBDIR = File.join($TOPDIR, 'lib')
11
11
  $:.unshift($LIBDIR)
12
12
  $:.unshift($EXTDIR)
13
13
 
14
+ # Setup SimpleCov for Ruby code coverage if COVERAGE env var is set
15
+ if ENV['COVERAGE']
16
+ begin
17
+ require 'simplecov'
18
+ require 'simplecov-lcov'
19
+
20
+ SimpleCov::Formatter::LcovFormatter.config.report_with_single_file = true
21
+ SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter.new([
22
+ SimpleCov::Formatter::HTMLFormatter,
23
+ SimpleCov::Formatter::LcovFormatter
24
+ ])
25
+
26
+ SimpleCov.start do
27
+ add_filter '/tests/'
28
+ add_filter '/spec/'
29
+ add_filter '/vendor/'
30
+ add_filter '/.bundle/'
31
+ add_group 'Library', 'lib'
32
+ add_group 'Extensions', 'ext'
33
+
34
+ # Track branch coverage if available
35
+ enable_coverage :branch if respond_to?(:enable_coverage)
36
+ end
37
+ rescue LoadError
38
+ puts "SimpleCov not available. Install it with: gem install simplecov simplecov-lcov"
39
+ end
40
+ end
41
+
14
42
  require 'curb'
15
43
  begin
16
44
  require 'test/unit'
@@ -19,6 +47,11 @@ rescue LoadError
19
47
  require 'test/unit'
20
48
  end
21
49
  require 'fileutils'
50
+ require 'rbconfig'
51
+
52
+ # Platform helpers
53
+ WINDOWS = /mswin|msys|mingw|cygwin|bccwin|wince|emc|windows/i.match?(RbConfig::CONFIG['host_os'])
54
+ NO_FORK = !Process.respond_to?(:fork)
22
55
 
23
56
  $TEST_URL = "file://#{'/' if RUBY_DESCRIPTION =~ /mswin|msys|mingw|cygwin|bccwin|wince|emc/}#{File.expand_path(__FILE__).tr('\\','/')}"
24
57
 
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)
@@ -32,6 +32,7 @@ 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
@@ -58,7 +59,7 @@ class TestCurbCurlDownload < Test::Unit::TestCase
58
59
  assert File.exist?(dl_path)
59
60
  assert_equal File.read(File.join(File.dirname(__FILE__), '..','ext','curb_easy.c')), File.read(dl_path)
60
61
  ensure
61
- File.unlink(dl_path) if File.exist?(dl_path)
62
+ File.unlink(dl_path) if dl_path && File.exist?(dl_path)
62
63
  end
63
64
 
64
65
  def test_download_bad_url_gives_404