ethon 0.5.12 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,48 @@
1
+ class RubyObjectLeakTracker
2
+ attr_reader :previous_count_hash, :current_count_hash
3
+
4
+ def initialize
5
+ @previous_count_hash = @current_count_hash = {}
6
+ end
7
+
8
+ def difference_between_runs(basis=@previous_count_hash)
9
+ @difference_between_runs ||= Hash[@current_count_hash.map do |object_class, count|
10
+ [object_class, count - (basis[object_class] || 0)]
11
+ end]
12
+ end
13
+
14
+ def total_difference_between_runs
15
+ difference_between_runs(@initial_count_hash).values.inject(0) { |sum, count| sum + count }
16
+ end
17
+
18
+ def capture_initial_memory_usage
19
+ capture_memory_usage
20
+ @initial_count_hash = @current_count_hash
21
+ end
22
+
23
+ def capture_memory_usage
24
+ @difference_between_runs = nil
25
+ @previous_count_hash = @current_count_hash
26
+
27
+ class_to_count = Hash.new { |hash, key| hash[key] = 0 }
28
+ ObjectSpace.each_object { |obj| class_to_count[obj.class] += 1 }
29
+
30
+ sorted_class_to_count = class_to_count.sort_by { |k, v| -v }
31
+ @current_count_hash = Hash[sorted_class_to_count]
32
+ end
33
+
34
+ def dump_status(logger)
35
+ diff = difference_between_runs
36
+ most_used_objects = current_count_hash.to_a.sort_by(&:last).reverse[0, 20]
37
+
38
+ most_used_objects.each do |object_class, count|
39
+ delta = diff[object_class]
40
+ logger.add(log_level(delta), sprintf("\t%s: %d (%+d)", object_class, count, delta))
41
+ end
42
+ end
43
+
44
+ private
45
+ def log_level(delta)
46
+ delta > 0 ? Logger::WARN : Logger::DEBUG
47
+ end
48
+ end
@@ -0,0 +1,27 @@
1
+ require 'spec_helper'
2
+
3
+ describe Ethon::Curl do
4
+ describe ".init" do
5
+ before { Ethon::Curl.send(:class_variable_set, :@@initialized, false) }
6
+
7
+ context "when global_init fails" do
8
+ it "raises global init error" do
9
+ Ethon::Curl.should_receive(:global_init).and_return(1)
10
+ expect{ Ethon::Curl.init }.to raise_error(Ethon::Errors::GlobalInit)
11
+ end
12
+ end
13
+
14
+ context "when global_init works" do
15
+ before { Ethon::Curl.should_receive(:global_init).and_return(0) }
16
+
17
+ it "doesn't raises global init error" do
18
+ expect{ Ethon::Curl.init }.to_not raise_error(Ethon::Errors::GlobalInit)
19
+ end
20
+
21
+ it "logs" do
22
+ Ethon.logger.should_receive(:debug)
23
+ Ethon::Curl.init
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,31 @@
1
+ require 'spec_helper'
2
+
3
+ describe Ethon::Easy::Callbacks do
4
+ let!(:easy) { Ethon::Easy.new }
5
+
6
+ describe "#set_callbacks" do
7
+ before do
8
+ Ethon::Curl.should_receive(:set_option).exactly(3).times
9
+ end
10
+
11
+ it "sets write- and headerfunction" do
12
+ easy.set_callbacks
13
+ end
14
+
15
+ it "resets @response_body" do
16
+ easy.set_callbacks
17
+ expect(easy.instance_variable_get(:@response_body)).to eq("")
18
+ end
19
+
20
+ it "resets @response_headers" do
21
+ easy.set_callbacks
22
+ expect(easy.instance_variable_get(:@response_headers)).to eq("")
23
+ end
24
+
25
+ it "resets @debug_info" do
26
+ easy.set_callbacks
27
+ expect(easy.instance_variable_get(:@debug_info).to_a).to eq([])
28
+ end
29
+
30
+ end
31
+ end
@@ -0,0 +1,52 @@
1
+ require 'spec_helper'
2
+
3
+ describe Ethon::Easy::DebugInfo do
4
+ let(:easy) { Ethon::Easy.new }
5
+
6
+ before do
7
+ easy.url = "http://localhost:3001/"
8
+ easy.perform
9
+ end
10
+
11
+ describe "#debug_info" do
12
+ context "when verbose is not set to true" do
13
+ it "does not save any debug info after a request" do
14
+ expect(easy.debug_info.to_a.length).to eq(0)
15
+ expect(easy.debug_info.to_h.values.flatten.length).to eq(0)
16
+ end
17
+ end
18
+
19
+ context "when verbose is set to true" do
20
+ before do
21
+ easy.verbose = true
22
+ easy.perform
23
+ end
24
+
25
+ after do
26
+ easy.reset
27
+ end
28
+
29
+ it "saves debug info after a request" do
30
+ expect(easy.debug_info.to_a.length).to be > 0
31
+ end
32
+
33
+ it "saves request headers" do
34
+ expect(easy.debug_info.header_out.join).to include('GET / HTTP/1.1')
35
+ end
36
+
37
+ it "saves response headers" do
38
+ expect(easy.debug_info.header_in.length).to be > 0
39
+ expect(easy.response_headers).to include(easy.debug_info.header_in.join)
40
+ end
41
+
42
+ it "saves incoming data" do
43
+ expect(easy.debug_info.data_in.length).to be > 0
44
+ expect(easy.response_body).to include(easy.debug_info.data_in.join)
45
+ end
46
+
47
+ it "saves debug text" do
48
+ expect(easy.debug_info.text.length).to be > 0
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,76 @@
1
+ require 'spec_helper'
2
+
3
+ describe Ethon::Easy::Form do
4
+ let(:hash) { {} }
5
+ let!(:easy) { Ethon::Easy.new }
6
+ let(:form) { Ethon::Easy::Form.new(easy, hash) }
7
+
8
+ describe ".new" do
9
+ it "assigns attribute to @params" do
10
+ expect(form.instance_variable_get(:@params)).to eq(hash)
11
+ end
12
+ end
13
+
14
+ describe "#first" do
15
+ it "returns a pointer" do
16
+ expect(form.first).to be_a(FFI::Pointer)
17
+ end
18
+ end
19
+
20
+ describe "#last" do
21
+ it "returns a pointer" do
22
+ expect(form.first).to be_a(FFI::Pointer)
23
+ end
24
+ end
25
+
26
+ describe "#multipart?" do
27
+ before { form.instance_variable_set(:@query_pairs, pairs) }
28
+
29
+ context "when query_pairs contains string values" do
30
+ let(:pairs) { [['a', '1'], ['b', '2']] }
31
+
32
+ it "returns false" do
33
+ expect(form.multipart?).to be_false
34
+ end
35
+ end
36
+
37
+ context "when query_pairs contains file" do
38
+ let(:pairs) { [['a', '1'], ['b', ['path', 'encoding', 'abs_path']]] }
39
+
40
+ it "returns true" do
41
+ expect(form.multipart?).to be_true
42
+ end
43
+ end
44
+ end
45
+
46
+ describe "#materialize" do
47
+ before { form.instance_variable_set(:@query_pairs, pairs) }
48
+
49
+ context "when query_pairs contains string values" do
50
+ let(:pairs) { [['a', '1']] }
51
+
52
+ it "adds params to form" do
53
+ Ethon::Curl.should_receive(:formadd)
54
+ form.materialize
55
+ end
56
+ end
57
+
58
+ context "when query_pairs contains nil" do
59
+ let(:pairs) { [['a', nil]] }
60
+
61
+ it "adds params to form" do
62
+ Ethon::Curl.should_receive(:formadd)
63
+ form.materialize
64
+ end
65
+ end
66
+
67
+ context "when query_pairs contains file" do
68
+ let(:pairs) { [['a', ["file", "type", "path/file"]]] }
69
+
70
+ it "adds file to form" do
71
+ Ethon::Curl.should_receive(:formadd)
72
+ form.materialize
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,78 @@
1
+ require 'spec_helper'
2
+
3
+ describe Ethon::Easy::Header do
4
+ let(:easy) { Ethon::Easy.new }
5
+
6
+ describe "#headers=" do
7
+ let(:headers) { { 'User-Agent' => 'Ethon' } }
8
+
9
+ it "sets header" do
10
+ Ethon::Easy.any_instance.should_receive(:set_callbacks)
11
+ Ethon::Curl.should_receive(:set_option)
12
+ easy.headers = headers
13
+ end
14
+
15
+ context "when requesting" do
16
+ before do
17
+ easy.headers = headers
18
+ easy.url = "http://localhost:3001"
19
+ easy.perform
20
+ end
21
+
22
+ it "sends" do
23
+ expect(easy.response_body).to include('"HTTP_USER_AGENT":"Ethon"')
24
+ end
25
+
26
+ context "when header value contains null byte" do
27
+ let(:headers) { { 'User-Agent' => "Ethon\0" } }
28
+
29
+ it "escapes" do
30
+ expect(easy.response_body).to include('"HTTP_USER_AGENT":"Ethon\\\\0"')
31
+ end
32
+ end
33
+
34
+ context "when header value has leading whitespace" do
35
+ let(:headers) { { 'User-Agent' => " Ethon" } }
36
+
37
+ it "removes" do
38
+ expect(easy.response_body).to include('"HTTP_USER_AGENT":"Ethon"')
39
+ end
40
+ end
41
+
42
+ context "when header value has traiing whitespace" do
43
+ let(:headers) { { 'User-Agent' => "Ethon " } }
44
+
45
+ it "removes" do
46
+ expect(easy.response_body).to include('"HTTP_USER_AGENT":"Ethon"')
47
+ end
48
+ end
49
+ end
50
+ end
51
+
52
+ describe "#compose_header" do
53
+ it "has space in between" do
54
+ expect(easy.compose_header('a', 'b')).to eq('a: b')
55
+ end
56
+
57
+ context "when value is a symbol" do
58
+ it "works" do
59
+ expect{ easy.compose_header('a', :b) }.to_not raise_error
60
+ end
61
+ end
62
+ end
63
+
64
+ describe "#header_list" do
65
+ context "when no set_headers" do
66
+ it "returns nil" do
67
+ expect(easy.header_list).to eq(nil)
68
+ end
69
+ end
70
+
71
+ context "when set_headers" do
72
+ it "returns pointer to header list" do
73
+ easy.headers = {'User-Agent' => 'Custom'}
74
+ expect(easy.header_list).to be_a(FFI::Pointer)
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,176 @@
1
+ require 'spec_helper'
2
+
3
+ describe Ethon::Easy::Http::Custom do
4
+ let(:easy) { Ethon::Easy.new }
5
+ let(:url) { "http://localhost:3001/" }
6
+ let(:params) { nil }
7
+ let(:form) { nil }
8
+ let(:custom) { described_class.new("PURGE", url, {:params => params, :body => form}) }
9
+
10
+ describe "#setup" do
11
+ context "when nothing" do
12
+ it "sets url" do
13
+ custom.setup(easy)
14
+ expect(easy.url).to eq(url)
15
+ end
16
+
17
+ it "makes a custom request" do
18
+ custom.setup(easy)
19
+ easy.perform
20
+ expect(easy.response_body).to include('"REQUEST_METHOD":"PURGE"')
21
+ end
22
+ end
23
+
24
+ context "when params" do
25
+ let(:params) { {:a => "1&"} }
26
+
27
+ it "attaches escaped to url" do
28
+ custom.setup(easy)
29
+ expect(easy.url).to eq("#{url}?a=1%26")
30
+ end
31
+
32
+ context "when requesting" do
33
+ before do
34
+ easy.headers = { 'Expect' => '' }
35
+ custom.setup(easy)
36
+ easy.perform
37
+ end
38
+
39
+ it "is a custom verb" do
40
+ expect(easy.response_body).to include('"REQUEST_METHOD":"PURGE"')
41
+ end
42
+
43
+ it "does not use application/x-www-form-urlencoded content type" do
44
+ expect(easy.response_body).to_not include('"CONTENT_TYPE":"application/x-www-form-urlencoded"')
45
+ end
46
+
47
+ it "requests parameterized url" do
48
+ expect(easy.response_body).to include('"REQUEST_URI":"http://localhost:3001/?a=1%26"')
49
+ end
50
+ end
51
+ end
52
+
53
+ context "when body" do
54
+ context "when multipart" do
55
+ let(:form) { {:a => File.open(__FILE__, 'r')} }
56
+
57
+ it "sets httppost" do
58
+ easy.should_receive(:httppost=)
59
+ custom.setup(easy)
60
+ end
61
+
62
+ context "when requesting" do
63
+ before do
64
+ easy.headers = { 'Expect' => '' }
65
+ custom.setup(easy)
66
+ easy.perform
67
+ end
68
+
69
+ it "returns ok" do
70
+ expect(easy.return_code).to eq(:ok)
71
+ end
72
+
73
+ it "is a custom verb" do
74
+ expect(easy.response_body).to include('"REQUEST_METHOD":"PURGE"')
75
+ end
76
+
77
+ it "uses multipart/form-data content type" do
78
+ expect(easy.response_body).to include('"CONTENT_TYPE":"multipart/form-data')
79
+ end
80
+
81
+ it "submits a body" do
82
+ expect(easy.response_body).to match('"body":".+"')
83
+ end
84
+
85
+ it "submits the data" do
86
+ expect(easy.response_body).to include('"filename":"custom_spec.rb"')
87
+ end
88
+ end
89
+ end
90
+
91
+ context "when not multipart" do
92
+ let(:form) { {:a => "1&b=2"} }
93
+ let(:encoded) { "a=1%26b%3D2" }
94
+
95
+ it "sets escaped copypostfields" do
96
+ easy.should_receive(:copypostfields=).with(encoded)
97
+ custom.setup(easy)
98
+ end
99
+
100
+ it "sets postfieldsize" do
101
+ easy.should_receive(:postfieldsize=).with{ |value| expect(value).to be(encoded.bytesize) }
102
+ custom.setup(easy)
103
+ end
104
+
105
+ context "when requesting" do
106
+ before do
107
+ easy.headers = { 'Expect' => '' }
108
+ custom.setup(easy)
109
+ easy.perform
110
+ end
111
+
112
+ it "returns ok" do
113
+ expect(easy.return_code).to eq(:ok)
114
+ end
115
+
116
+ it "is a custom verb" do
117
+ expect(easy.response_body).to include('"REQUEST_METHOD":"PURGE"')
118
+ end
119
+
120
+ it "uses multipart/form-data content type" do
121
+ expect(easy.response_body).to include('"CONTENT_TYPE":"application/x-www-form-urlencoded')
122
+ end
123
+
124
+ it "submits a body" do
125
+ expect(easy.response_body).to match('"body":"a=1%26b%3D2"')
126
+ end
127
+
128
+ it "submits the data" do
129
+ expect(easy.response_body).to include('"rack.request.form_hash":{"a":"1&b=2"}')
130
+ end
131
+ end
132
+ end
133
+
134
+ context "when string" do
135
+ let(:form) { "{a: 1}" }
136
+
137
+ context "when requesting" do
138
+ before do
139
+ easy.headers = { 'Expect' => '' }
140
+ custom.setup(easy)
141
+ easy.perform
142
+ end
143
+
144
+ it "returns ok" do
145
+ expect(easy.return_code).to eq(:ok)
146
+ end
147
+
148
+ it "sends string" do
149
+ expect(easy.response_body).to include('"body":"{a: 1}"')
150
+ end
151
+ end
152
+ end
153
+ end
154
+
155
+ context "when params and body" do
156
+ let(:form) { {:a => "1"} }
157
+ let(:params) { {:b => "2"} }
158
+
159
+ context "when requesting" do
160
+ before do
161
+ easy.headers = { 'Expect' => '' }
162
+ custom.setup(easy)
163
+ easy.perform
164
+ end
165
+
166
+ it "url contains params" do
167
+ expect(easy.response_body).to include('"REQUEST_URI":"http://localhost:3001/?b=2"')
168
+ end
169
+
170
+ it "body contains form" do
171
+ expect(easy.response_body).to include('"body":"a=1"')
172
+ end
173
+ end
174
+ end
175
+ end
176
+ end