ethon 0.5.12 → 0.6.0

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 (66) hide show
  1. checksums.yaml +15 -0
  2. data/.gitignore +7 -0
  3. data/.rspec +3 -0
  4. data/.travis.yml +11 -0
  5. data/CHANGELOG.md +5 -0
  6. data/Gemfile +1 -1
  7. data/Guardfile +9 -0
  8. data/ethon.gemspec +26 -0
  9. data/lib/ethon/curl.rb +0 -12
  10. data/lib/ethon/curls/constants.rb +6 -22
  11. data/lib/ethon/curls/functions.rb +38 -41
  12. data/lib/ethon/curls/infos.rb +19 -0
  13. data/lib/ethon/curls/options.rb +416 -219
  14. data/lib/ethon/curls/settings.rb +1 -0
  15. data/lib/ethon/easy.rb +12 -18
  16. data/lib/ethon/easy/callbacks.rb +40 -6
  17. data/lib/ethon/easy/debug_info.rb +46 -0
  18. data/lib/ethon/easy/mirror.rb +39 -0
  19. data/lib/ethon/easy/options.rb +17 -1235
  20. data/lib/ethon/easy/queryable.rb +6 -8
  21. data/lib/ethon/easy/response_callbacks.rb +1 -1
  22. data/lib/ethon/version.rb +1 -1
  23. data/profile/benchmarks.rb +137 -0
  24. data/profile/memory_leaks.rb +113 -0
  25. data/profile/perf_spec_helper.rb +36 -0
  26. data/profile/support/memory_test_helpers.rb +75 -0
  27. data/profile/support/os_memory_leak_tracker.rb +47 -0
  28. data/profile/support/ruby_object_leak_tracker.rb +48 -0
  29. data/spec/ethon/curl_spec.rb +27 -0
  30. data/spec/ethon/easy/callbacks_spec.rb +31 -0
  31. data/spec/ethon/easy/debug_info_spec.rb +52 -0
  32. data/spec/ethon/easy/form_spec.rb +76 -0
  33. data/spec/ethon/easy/header_spec.rb +78 -0
  34. data/spec/ethon/easy/http/custom_spec.rb +176 -0
  35. data/spec/ethon/easy/http/delete_spec.rb +20 -0
  36. data/spec/ethon/easy/http/get_spec.rb +89 -0
  37. data/spec/ethon/easy/http/head_spec.rb +79 -0
  38. data/spec/ethon/easy/http/options_spec.rb +50 -0
  39. data/spec/ethon/easy/http/patch_spec.rb +50 -0
  40. data/spec/ethon/easy/http/post_spec.rb +220 -0
  41. data/spec/ethon/easy/http/put_spec.rb +124 -0
  42. data/spec/ethon/easy/http_spec.rb +44 -0
  43. data/spec/ethon/easy/informations_spec.rb +82 -0
  44. data/spec/ethon/easy/mirror_spec.rb +39 -0
  45. data/spec/ethon/easy/operations_spec.rb +251 -0
  46. data/spec/ethon/easy/options_spec.rb +135 -0
  47. data/spec/ethon/easy/queryable_spec.rb +188 -0
  48. data/spec/ethon/easy/response_callbacks_spec.rb +50 -0
  49. data/spec/ethon/easy/util_spec.rb +27 -0
  50. data/spec/ethon/easy_spec.rb +105 -0
  51. data/spec/ethon/libc_spec.rb +13 -0
  52. data/spec/ethon/loggable_spec.rb +21 -0
  53. data/spec/ethon/multi/operations_spec.rb +297 -0
  54. data/spec/ethon/multi/options_spec.rb +70 -0
  55. data/spec/ethon/multi/stack_spec.rb +79 -0
  56. data/spec/ethon/multi_spec.rb +21 -0
  57. data/spec/spec_helper.rb +27 -0
  58. data/spec/support/localhost_server.rb +94 -0
  59. data/spec/support/server.rb +114 -0
  60. metadata +91 -31
  61. data/lib/ethon/curls/auth_types.rb +0 -25
  62. data/lib/ethon/curls/http_versions.rb +0 -22
  63. data/lib/ethon/curls/postredir.rb +0 -15
  64. data/lib/ethon/curls/protocols.rb +0 -36
  65. data/lib/ethon/curls/proxy_types.rb +0 -25
  66. data/lib/ethon/curls/ssl_versions.rb +0 -23
@@ -10,12 +10,12 @@ module Ethon
10
10
  base.send(:attr_accessor, :escape)
11
11
  end
12
12
 
13
- # Return wether there are elements in the form or not.
13
+ # Return wether there are elements in params or not.
14
14
  #
15
- # @example Return if form is empty.
15
+ # @example Return if params is empty.
16
16
  # form.empty?
17
17
  #
18
- # @return [ Boolean ] True if form is empty, else false.
18
+ # @return [ Boolean ] True if params is empty, else false.
19
19
  def empty?
20
20
  @params.empty?
21
21
  end
@@ -102,14 +102,12 @@ module Ethon
102
102
 
103
103
  def pairs_for(v, key, pairs)
104
104
  case v
105
- when Hash
106
- recursively_generate_pairs(v, key, pairs)
107
- when Array
105
+ when Hash, Array
108
106
  recursively_generate_pairs(v, key, pairs)
109
107
  when File, Tempfile
110
- pairs << [Util.escape_zero_byte(key), file_info(v)]
108
+ pairs << [key, file_info(v)]
111
109
  else
112
- pairs << [Util.escape_zero_byte(key), Util.escape_zero_byte(v)]
110
+ pairs << [key, v]
113
111
  end
114
112
  end
115
113
  end
@@ -39,7 +39,7 @@ module Ethon
39
39
  # @example Execute on_completes.
40
40
  # request.complete
41
41
  def complete
42
- if defined?(@on_complete)
42
+ if @on_complete
43
43
  @on_complete.each{ |callback| callback.call(self) }
44
44
  end
45
45
  end
data/lib/ethon/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  module Ethon
2
2
 
3
3
  # Ethon version.
4
- VERSION = '0.5.12'
4
+ VERSION = '0.6.0'
5
5
  end
@@ -0,0 +1,137 @@
1
+ # encoding: utf-8
2
+ require 'ethon'
3
+ require 'open-uri'
4
+ require 'patron'
5
+ require 'curb'
6
+ require "net/http"
7
+ require 'cgi'
8
+ require 'benchmark'
9
+
10
+ Benchmark.bm do |bm|
11
+
12
+ [100_000].each do |i|
13
+ puts "[ #{i} Creations]"
14
+
15
+ bm.report("String.new ") do
16
+ i.times { String.new }
17
+ end
18
+
19
+ bm.report("Easy.new ") do
20
+ i.times { Ethon::Easy.new }
21
+ end
22
+ end
23
+
24
+ GC.start
25
+
26
+ [100_000].each do |i|
27
+ puts "[ #{i} Escapes]"
28
+
29
+ bm.report("CGI::escape ") do
30
+ i.times { CGI::escape("まつもと") }
31
+ end
32
+
33
+ bm.report("Easy.escape ") do
34
+ e = Ethon::Easy.new
35
+ i.times { e.escape("まつもと") }
36
+ end
37
+ end
38
+
39
+ GC.start
40
+
41
+ [1000].each do |i|
42
+ puts "[ #{i} Requests]"
43
+
44
+ bm.report("net/http ") do
45
+ uri = URI.parse("http://localhost:3001/")
46
+ i.times { Net::HTTP.get_response(uri) }
47
+ end
48
+
49
+ bm.report("open-uri ") do
50
+ i.times { open "http://localhost:3001/" }
51
+ end
52
+
53
+ bm.report("patron ") do
54
+ sess = Patron::Session.new
55
+ i.times do
56
+ sess.base_url = "http://localhost:3001"
57
+ sess.get("/")
58
+ end
59
+ end
60
+
61
+ bm.report("patron reuse ") do
62
+ sess = Patron::Session.new
63
+ sess.base_url = "http://localhost:3001"
64
+ i.times do
65
+ sess.get("/")
66
+ end
67
+ end
68
+
69
+ bm.report("curb reuse ") do
70
+ easy = Curl::Easy.new("http://localhost:3001")
71
+ i.times do
72
+ easy.perform
73
+ end
74
+ end
75
+
76
+ bm.report("Easy.perform ") do
77
+ easy = Ethon::Easy.new
78
+ i.times do
79
+ easy.url = "http://localhost:3001/"
80
+ easy.prepare
81
+ easy.perform
82
+ end
83
+ end
84
+
85
+ bm.report("Easy.perform reuse") do
86
+ easy = Ethon::Easy.new
87
+ easy.url = "http://localhost:3001/"
88
+ easy.prepare
89
+ i.times { easy.perform }
90
+ end
91
+ end
92
+
93
+ GC.start
94
+
95
+ puts "[ 4 delayed Requests ]"
96
+
97
+ bm.report("net/http ") do
98
+ 3.times do |i|
99
+ uri = URI.parse("http://localhost:300#{i}/?delay=1")
100
+ Net::HTTP.get_response(uri)
101
+ end
102
+ end
103
+
104
+ bm.report("open-uri ") do
105
+ 3.times do |i|
106
+ open("http://localhost:300#{i}/?delay=1")
107
+ end
108
+ end
109
+
110
+ bm.report("patron ") do
111
+ sess = Patron::Session.new
112
+ 3.times do |i|
113
+ sess.base_url = "http://localhost:300#{i}/?delay=1"
114
+ sess.get("/")
115
+ end
116
+ end
117
+
118
+ bm.report("Easy.perform ") do
119
+ easy = Ethon::Easy.new
120
+ 3.times do |i|
121
+ easy.url = "http://localhost:300#{i}/?delay=1"
122
+ easy.prepare
123
+ easy.perform
124
+ end
125
+ end
126
+
127
+ bm.report("Multi.perform ") do
128
+ multi = Ethon::Multi.new
129
+ 3.times do |i|
130
+ easy = Ethon::Easy.new
131
+ easy.url = "http://localhost:300#{i}/?delay=1"
132
+ easy.prepare
133
+ multi.add(easy)
134
+ end
135
+ multi.perform
136
+ end
137
+ end
@@ -0,0 +1,113 @@
1
+ require 'ethon'
2
+ require 'ethon/easy'
3
+
4
+ require_relative 'perf_spec_helper'
5
+ require 'rspec/autorun'
6
+
7
+ describe "low-level interactions with libcurl" do
8
+ describe Ethon::Multi do
9
+ memory_leak_test("init") do
10
+ Ethon::Multi.new
11
+ end
12
+
13
+ memory_leak_test("handle") do
14
+ Ethon::Multi.new.handle
15
+ end
16
+ end
17
+
18
+ describe Ethon::Easy do
19
+ memory_leak_test("init") do
20
+ Ethon::Easy.new
21
+ end
22
+
23
+ memory_leak_test("handle") do
24
+ Ethon::Easy.new.handle
25
+ end
26
+
27
+ memory_leak_test("headers") do
28
+ Ethon::Easy.new.headers = { "a" => 1, "b" => 2, "c" => 3, "d" => 4}
29
+ end
30
+
31
+ memory_leak_test("escape") do
32
+ Ethon::Easy.new.escape("the_sky&is_blue")
33
+ end
34
+ end
35
+
36
+
37
+ describe Ethon::Easy::Form do
38
+ memory_leak_test("init") do
39
+ Ethon::Easy::Form.new(nil, {})
40
+ end
41
+
42
+ memory_leak_test("first") do
43
+ Ethon::Easy::Form.new(nil, {}).first
44
+ end
45
+
46
+ memory_leak_test("last") do
47
+ Ethon::Easy::Form.new(nil, {}).last
48
+ end
49
+
50
+ memory_leak_test("materialized with some params") do
51
+ form = Ethon::Easy::Form.new(nil, { "a" => "1" })
52
+ form.materialize
53
+ end
54
+
55
+ memory_leak_test("materialized with a file") do
56
+ File.open(__FILE__, "r") do |file|
57
+ form = Ethon::Easy::Form.new(nil, { "a" => file })
58
+ form.materialize
59
+ end
60
+ end
61
+ end
62
+ end
63
+
64
+ describe "higher level operations" do
65
+ memory_leak_test("a simple request") do
66
+ Ethon::Easy.new(:url => "http://localhost:3001/",
67
+ :forbid_reuse => true).perform
68
+ end
69
+
70
+ memory_leak_test("a request with headers") do
71
+ Ethon::Easy.new(:url => "http://localhost:3001/",
72
+ :headers => { "Content-Type" => "application/json",
73
+ "Something" => "1",
74
+ "Else" => "qwerty",
75
+ "Long-String" => "aassddffgghhiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz"},
76
+ :forbid_reuse => true).perform
77
+ end
78
+
79
+ memory_leak_test("a request with headers and params") do
80
+ easy = Ethon::Easy.new(:url => "http://localhost:3001/",
81
+ :headers => { "Content-Type" => "application/json",
82
+ "Something" => "1",
83
+ "Else" => "qwerty",
84
+ "Long-String" => "aassddffgghhiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz"},
85
+ :forbid_reuse => true)
86
+ easy.http_request("http://localhost:3001/",
87
+ :get,
88
+ :params => { "param1" => "value1",
89
+ "param2" => "value2",
90
+ "param3" => "value3",
91
+ "param4" => "value4"})
92
+ end
93
+
94
+ memory_leak_test("a request with headers, params, and body") do
95
+ easy = Ethon::Easy.new(:url => "http://localhost:3001/",
96
+ :headers => { "Content-Type" => "application/json",
97
+ "Something" => "1",
98
+ "Else" => "qwerty",
99
+ "Long-String" => "aassddffgghhiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz"},
100
+ :forbid_reuse => true)
101
+ easy.http_request("http://localhost:3001/",
102
+ :get,
103
+ :params => { "param1" => "value1",
104
+ "param2" => "value2",
105
+ "param3" => "value3",
106
+ "param4" => "value4"},
107
+ :body => {
108
+ "body1" => "value1",
109
+ "body2" => "value2",
110
+ "body3" => "value3"
111
+ })
112
+ end
113
+ end
@@ -0,0 +1,36 @@
1
+ #### SETUP
2
+ require 'bundler'
3
+ Bundler.setup
4
+ require 'rspec'
5
+
6
+ require 'support/localhost_server'
7
+ require 'support/server'
8
+ require_relative 'support/memory_test_helpers'
9
+
10
+ require 'logger'
11
+
12
+ if ENV['VERBOSE']
13
+ Ethon.logger = Logger.new($stdout)
14
+ Ethon.logger.level = Logger::DEBUG
15
+ end
16
+
17
+ RSpec.configure do |config|
18
+ config.before(:suite) do
19
+ LocalhostServer.new(TESTSERVER.new, 3001)
20
+ end
21
+ config.include(MemoryTestHelpers)
22
+ config.extend(MemoryTestHelpers::TestMethods)
23
+ end
24
+
25
+ MemoryTestHelpers.setup
26
+ MemoryTestHelpers.logger = Logger.new($stdout)
27
+ MemoryTestHelpers.logger.level = Logger::INFO
28
+ MemoryTestHelpers.logger.formatter = proc do |severity, datetime, progname, msg|
29
+ "\t\t#{msg}\n"
30
+ end
31
+
32
+ if ENV['VERBOSE']
33
+ MemoryTestHelpers.logger.level = Logger::DEBUG
34
+ end
35
+
36
+ MemoryTestHelpers.iterations = ENV.fetch("ITERATIONS", 10_000).to_i
@@ -0,0 +1,75 @@
1
+ require_relative 'ruby_object_leak_tracker'
2
+ require_relative 'os_memory_leak_tracker'
3
+
4
+ module MemoryTestHelpers
5
+ class << self
6
+ attr_accessor :gc_proc, :iterations, :logger
7
+
8
+ def setup
9
+ if RUBY_PLATFORM == "java"
10
+ # for leak detection
11
+ JRuby.objectspace = true if defined?(JRuby)
12
+ # for gc
13
+ require 'java'
14
+ java_import 'java.lang.System'
15
+ self.gc_proc = proc { System.gc }
16
+ else
17
+ self.gc_proc = proc { GC.start }
18
+ end
19
+ end
20
+ end
21
+
22
+ module TestMethods
23
+ def memory_leak_test(description, &block)
24
+ context(description) do
25
+ it "doesn't leak ruby objects" do
26
+ object_leak_tracker = RubyObjectLeakTracker.new
27
+ track_memory_usage(object_leak_tracker, &block)
28
+ object_leak_tracker.total_difference_between_runs.should be <= 10
29
+ end
30
+
31
+ it "doesn't leak OS memory (C interop check)" do
32
+ os_memory_leak_tracker = OSMemoryLeakTracker.new
33
+ track_memory_usage(os_memory_leak_tracker, &block)
34
+ os_memory_leak_tracker.total_difference_between_runs.should be <= 10
35
+ end
36
+ end
37
+ end
38
+ end
39
+
40
+ def track_memory_usage(tracker)
41
+ # Intentionally do all this setup before we do any testing
42
+ logger = MemoryTestHelpers.logger
43
+ iterations = MemoryTestHelpers.iterations
44
+
45
+ checkpoint_frequency = (iterations / 10.0).to_i
46
+ gc_frequency = 20
47
+
48
+ warmup_iterations = [(iterations / 3.0).to_i, 500].min
49
+ logger.info "Performing #{warmup_iterations} warmup iterations"
50
+ warmup_iterations.times do
51
+ yield
52
+ MemoryTestHelpers.gc_proc.call
53
+ end
54
+ tracker.capture_initial_memory_usage
55
+
56
+ logger.info "Performing #{iterations} iterations (checkpoint every #{checkpoint_frequency})"
57
+
58
+ iterations.times do |i|
59
+ yield
60
+
61
+ last_iteration = (i == iterations - 1)
62
+ checkpoint = last_iteration || (i % checkpoint_frequency == 0)
63
+
64
+ if checkpoint || (i % gc_frequency == 0)
65
+ MemoryTestHelpers.gc_proc.call
66
+ end
67
+
68
+ if checkpoint
69
+ logger.info "Iteration #{i} checkpoint"
70
+ tracker.capture_memory_usage
71
+ tracker.dump_status(logger)
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,47 @@
1
+ class OSMemoryLeakTracker
2
+ attr_reader :current_run
3
+
4
+ def initialize
5
+ @previous_run = @current_run = 0
6
+ end
7
+
8
+ def difference_between_runs(basis=@previous_run)
9
+ @current_run - basis
10
+ end
11
+
12
+ def total_difference_between_runs
13
+ difference_between_runs(@initial_count_run)
14
+ end
15
+
16
+ def capture_initial_memory_usage
17
+ capture_memory_usage
18
+ @initial_count_run = @current_run
19
+ end
20
+
21
+ def capture_memory_usage
22
+ @previous_run = @current_run
23
+ @current_run = rss_bytes
24
+ end
25
+
26
+ def dump_status(logger)
27
+ delta = difference_between_runs
28
+ logger.add(log_level(delta), sprintf("\tTotal memory usage (kb): %d (%+d)", current_run, delta))
29
+ end
30
+
31
+ private
32
+ # amount of memory the current process "is using", in RAM
33
+ # (doesn't include any swap memory that it may be using, just that in actual RAM)
34
+ # Code loosely based on https://github.com/rdp/os/blob/master/lib/os.rb
35
+ # returns 0 on windows
36
+ def rss_bytes
37
+ if ENV['OS'] == 'Windows_NT'
38
+ 0
39
+ else
40
+ `ps -o rss= -p #{Process.pid}`.to_i # in kilobytes
41
+ end
42
+ end
43
+
44
+ def log_level(delta)
45
+ delta > 0 ? Logger::WARN : Logger::DEBUG
46
+ end
47
+ end