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,135 @@
1
+ require 'spec_helper'
2
+
3
+ describe Ethon::Easy::Options do
4
+ let(:easy) { Ethon::Easy.new }
5
+
6
+ [
7
+ :accept_encoding, :cainfo, :capath, :connecttimeout, :connecttimeout_ms, :cookie,
8
+ :cookiejar, :cookiefile, :copypostfields, :customrequest, :dns_cache_timeout,
9
+ :followlocation, :forbid_reuse, :http_version, :httpauth, :httpget, :httppost,
10
+ :infilesize, :interface, :keypasswd, :maxredirs, :nobody, :nosignal,
11
+ :postfieldsize, :postredir, :protocols, :proxy, :proxyauth, :proxyport, :proxytype,
12
+ :proxyuserpwd, :readdata, :readfunction, :redir_protocols, :ssl_verifyhost,
13
+ :ssl_verifypeer, :sslcert, :sslcerttype, :sslkey, :sslkeytype, :sslversion,
14
+ :timeout, :timeout_ms, :unrestricted_auth, :upload, :url, :useragent,
15
+ :userpwd, :verbose
16
+ ].each do |name|
17
+ describe "#{name}=" do
18
+ it "responds_to" do
19
+ expect(easy).to respond_to("#{name}=")
20
+ end
21
+
22
+ it "sets option" do
23
+ Ethon::Easy.any_instance.should_receive(:set_callbacks)
24
+ Ethon::Curl.should_receive(:set_option).with do |option, _, _|
25
+ expect(option).to be(name)
26
+ end
27
+ value = case name
28
+ when :http_version
29
+ :httpv1_0
30
+ when :httpauth
31
+ :basic
32
+ when :protocols, :redir_protocols
33
+ :http
34
+ when :postredir
35
+ :post_301
36
+ when :proxytype
37
+ :http
38
+ when :sslversion
39
+ :default
40
+ when :httppost
41
+ FFI::Pointer::NULL
42
+ else
43
+ 1
44
+ end
45
+ easy.method("#{name}=").call(value)
46
+ end
47
+ end
48
+ end
49
+
50
+ describe "#httppost=" do
51
+ it "raises unless given a FFI::Pointer" do
52
+ expect{ easy.httppost = 1 }.to raise_error(Ethon::Errors::InvalidValue)
53
+ end
54
+ end
55
+
56
+ context "when requesting" do
57
+ let(:url) { "localhost:3001" }
58
+ let(:timeout) { nil }
59
+ let(:timeout_ms) { nil }
60
+ let(:connecttimeout) { nil }
61
+ let(:connecttimeout_ms) { nil }
62
+ let(:userpwd) { nil }
63
+
64
+ before do
65
+ easy.url = url
66
+ easy.timeout = timeout
67
+ easy.timeout_ms = timeout_ms
68
+ easy.connecttimeout = connecttimeout
69
+ easy.connecttimeout_ms = connecttimeout_ms
70
+ easy.userpwd = userpwd
71
+ easy.perform
72
+ end
73
+
74
+ context "when userpwd" do
75
+ context "when contains /" do
76
+ let(:url) { "localhost:3001/auth_basic/test/te%2Fst" }
77
+ let(:userpwd) { "test:te/st" }
78
+
79
+ it "works" do
80
+ expect(easy.response_code).to eq(200)
81
+ end
82
+ end
83
+ end
84
+
85
+ context "when timeout" do
86
+ let(:timeout) { 1 }
87
+
88
+ context "when request takes longer" do
89
+ let(:url) { "localhost:3001?delay=2" }
90
+
91
+ it "times out" do
92
+ expect(easy.return_code).to eq(:operation_timedout)
93
+ end
94
+ end
95
+ end
96
+
97
+ context "when connecttimeout" do
98
+ let(:connecttimeout) { 1 }
99
+
100
+ context "when cannot connect" do
101
+ let(:url) { "localhost:3002" }
102
+
103
+ it "times out" do
104
+ expect(easy.return_code).to eq(:couldnt_connect)
105
+ end
106
+ end
107
+ end
108
+
109
+ if Ethon::Curl.version.match("c-ares")
110
+ context "when timeout_ms" do
111
+ let(:timeout_ms) { 900 }
112
+
113
+ context "when request takes longer" do
114
+ let(:url) { "localhost:3001?delay=1" }
115
+
116
+ it "times out" do
117
+ expect(easy.return_code).to eq(:operation_timedout)
118
+ end
119
+ end
120
+ end
121
+
122
+ context "when connecttimeout_ms" do
123
+ let(:connecttimeout_ms) { 1 }
124
+
125
+ context "when cannot connect" do
126
+ let(:url) { "localhost:3002" }
127
+
128
+ it "times out" do
129
+ expect(easy.return_code).to eq(:couldnt_connect)
130
+ end
131
+ end
132
+ end
133
+ end
134
+ end
135
+ end
@@ -0,0 +1,188 @@
1
+ # encoding: utf-8
2
+ require 'spec_helper'
3
+
4
+ describe Ethon::Easy::Queryable do
5
+ let(:hash) { {} }
6
+ let!(:easy) { Ethon::Easy.new }
7
+ let(:params) { Ethon::Easy::Params.new(easy, hash) }
8
+
9
+ describe "#to_s" do
10
+ context "when query_pairs empty" do
11
+ before { params.instance_variable_set(:@query_pairs, []) }
12
+
13
+ it "returns empty string" do
14
+ expect(params.to_s).to eq("")
15
+ end
16
+ end
17
+
18
+ context "when query_pairs not empty" do
19
+ context "when escape" do
20
+ before do
21
+ params.escape = true
22
+ end
23
+
24
+ {
25
+ '!' => '%21', '*' => '%2A', "'" => '%27', '(' => '%28',
26
+ ')' => '%29', ';' => '%3B', ':' => '%3A', '@' => '%40',
27
+ '&' => '%26', '=' => '%3D', '+' => '%2B', '$' => '%24',
28
+ ',' => '%2C', '/' => '%2F', '?' => '%3F', '#' => '%23',
29
+ '[' => '%5B', ']' => '%5D',
30
+
31
+ '<' => '%3C', '>' => '%3E', '"' => '%22', '{' => '%7B',
32
+ '}' => '%7D', '|' => '%7C', '\\' => '%5C', '`' => '%60',
33
+ '^' => '%5E', '%' => '%25', ' ' => '%20', "\0" => '%00',
34
+
35
+ 'まつもと' => '%E3%81%BE%E3%81%A4%E3%82%82%E3%81%A8',
36
+ }.each do |value, percent|
37
+ it "turns #{value.inspect} into #{percent}" do
38
+ params.instance_variable_set(:@query_pairs, [[:a, value]])
39
+ expect(params.to_s).to eq("a=#{percent}")
40
+ end
41
+ end
42
+
43
+ {
44
+ '.' => '%2E', '-' => '%2D', '_' => '%5F', '~' => '%7E',
45
+ }.each do |value, percent|
46
+ it "leaves #{value.inspect} instead of turning into #{percent}" do
47
+ params.instance_variable_set(:@query_pairs, [[:a, value]])
48
+ expect(params.to_s).to eq("a=#{value}")
49
+ end
50
+ end
51
+ end
52
+
53
+ context "when no escape" do
54
+ before { params.instance_variable_set(:@query_pairs, [[:a, 1], [:b, 2]]) }
55
+
56
+ it "returns concatenated query string" do
57
+ expect(params.to_s).to eq("a=1&b=2")
58
+ end
59
+ end
60
+ end
61
+
62
+ context "when query_pairs contains a string" do
63
+ before { params.instance_variable_set(:@query_pairs, ["{a: 1}"]) }
64
+
65
+ it "returns correct string" do
66
+ expect(params.to_s).to eq("{a: 1}")
67
+ end
68
+ end
69
+ end
70
+
71
+ describe "#build_query_pairs" do
72
+ let(:pairs) { params.method(:build_query_pairs).call(hash) }
73
+
74
+ context "when params is empty" do
75
+ it "returns empty array" do
76
+ expect(pairs).to eq([])
77
+ end
78
+ end
79
+
80
+ context "when params is string" do
81
+ let(:hash) { "{a: 1}" }
82
+
83
+ it "wraps it in an array" do
84
+ expect(pairs).to eq([hash])
85
+ end
86
+ end
87
+
88
+ context "when params is simple hash" do
89
+ let(:hash) { {:a => 1, :b => 2} }
90
+
91
+ it "transforms" do
92
+ expect(pairs).to include([:a, 1])
93
+ expect(pairs).to include([:b, 2])
94
+ end
95
+ end
96
+
97
+ context "when params is a nested hash" do
98
+ let(:hash) { {:a => 1, :b => {:c => 2}} }
99
+
100
+ it "transforms" do
101
+ expect(pairs).to include([:a, 1])
102
+ expect(pairs).to include(["b[c]", 2])
103
+ end
104
+ end
105
+
106
+ context "when params contains an array" do
107
+ let(:hash) { {:a => 1, :b => [2, 3]} }
108
+
109
+ it "transforms" do
110
+ expect(pairs).to include([:a, 1])
111
+ expect(pairs).to include(["b[0]", 2])
112
+ expect(pairs).to include(["b[1]", 3])
113
+ end
114
+ end
115
+
116
+ context "when params contains something nested in an array" do
117
+ context "when string" do
118
+ let(:hash) { {:a => {:b => ["hello", "world"]}} }
119
+
120
+ it "transforms" do
121
+ expect(pairs).to eq([["a[b][0]", "hello"], ["a[b][1]", "world"]])
122
+ end
123
+ end
124
+
125
+ context "when hash" do
126
+ let(:hash) { {:a => {:b => [{:c =>1}, {:d => 2}]}} }
127
+
128
+ it "transforms" do
129
+ expect(pairs).to eq([["a[b][0][c]", 1], ["a[b][1][d]", 2]])
130
+ end
131
+ end
132
+
133
+ context "when file" do
134
+ let(:file) { Tempfile.new("fubar") }
135
+ let(:file_info) { params.method(:file_info).call(file) }
136
+ let(:hash) { {:a => {:b => [file]}} }
137
+
138
+ it "transforms" do
139
+ expect(pairs).to eq([["a[b][0]", file_info]])
140
+ end
141
+ end
142
+ end
143
+
144
+
145
+ context "when params contains file" do
146
+ let(:file) { Tempfile.new("fubar") }
147
+ let(:file_info) { params.method(:file_info).call(file) }
148
+ let(:hash) { {:a => 1, :b => file} }
149
+
150
+ it "transforms" do
151
+ expect(pairs).to include([:a, 1])
152
+ expect(pairs).to include([:b, file_info])
153
+ end
154
+ end
155
+
156
+ context "when params key contains a null byte" do
157
+ let(:hash) { {:a => "1\0" } }
158
+
159
+ it "preserves" do
160
+ expect(pairs).to eq([[:a, "1\0"]])
161
+ end
162
+ end
163
+
164
+ context "when params value contains a null byte" do
165
+ let(:hash) { {"a\0" => 1 } }
166
+
167
+ it "preserves" do
168
+ expect(pairs).to eq([["a\0", 1]])
169
+ end
170
+ end
171
+ end
172
+
173
+ describe "#empty?" do
174
+ context "when params empty" do
175
+ it "returns true" do
176
+ expect(params.empty?).to be_true
177
+ end
178
+ end
179
+
180
+ context "when params not empty" do
181
+ let(:hash) { {:a => 1} }
182
+
183
+ it "returns false" do
184
+ expect(params.empty?).to be_false
185
+ end
186
+ end
187
+ end
188
+ end
@@ -0,0 +1,50 @@
1
+ require 'spec_helper'
2
+
3
+ describe Ethon::Easy::ResponseCallbacks do
4
+ let(:easy) { Ethon::Easy.new }
5
+
6
+ describe "#on_complete" do
7
+ it "responds" do
8
+ expect(easy).to respond_to(:on_complete)
9
+ end
10
+
11
+ context "when no block given" do
12
+ it "returns @on_complete" do
13
+ expect(easy.on_complete).to eq([])
14
+ end
15
+ end
16
+
17
+ context "when block given" do
18
+ it "stores" do
19
+ easy.on_complete { p 1 }
20
+ expect(easy.instance_variable_get(:@on_complete)).to have(1).items
21
+ end
22
+ end
23
+
24
+ context "when multiple blocks given" do
25
+ it "stores" do
26
+ easy.on_complete { p 1 }
27
+ easy.on_complete { p 2 }
28
+ expect(easy.instance_variable_get(:@on_complete)).to have(2).items
29
+ end
30
+ end
31
+ end
32
+
33
+ describe "#complete" do
34
+ before do
35
+ easy.on_complete {|r| String.new(r.url) }
36
+ end
37
+
38
+ it "executes blocks and passes self" do
39
+ String.should_receive(:new).with(easy.url)
40
+ easy.complete
41
+ end
42
+
43
+ context "when @on_complete nil" do
44
+ it "doesn't raise" do
45
+ easy.instance_variable_set(:@on_complete, nil)
46
+ expect{ easy.complete }.to_not raise_error(NoMethodError)
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,27 @@
1
+ require 'spec_helper'
2
+
3
+ describe Ethon::Easy::Util do
4
+ class Dummy
5
+ include Ethon::Easy::Util
6
+ end
7
+
8
+ let(:klass) { Dummy.new }
9
+
10
+ describe "escape_zero_byte" do
11
+ context "when value has no zero byte" do
12
+ let(:value) { "hello world" }
13
+
14
+ it "returns same value" do
15
+ expect(klass.escape_zero_byte(value)).to be(value)
16
+ end
17
+ end
18
+
19
+ context "when value has zero byte" do
20
+ let(:value) { "hello \0world" }
21
+
22
+ it "returns escaped" do
23
+ expect(klass.escape_zero_byte(value)).to eq("hello \\0world")
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,105 @@
1
+ require 'spec_helper'
2
+
3
+ describe Ethon::Easy do
4
+ let(:easy) { Ethon::Easy.new }
5
+
6
+ describe ".new" do
7
+ it "inits curl" do
8
+ Ethon::Curl.should_receive(:init)
9
+ easy
10
+ end
11
+
12
+ context "when options are empty" do
13
+ it "sets only callbacks" do
14
+ Ethon::Easy.any_instance.should_receive(:set_callbacks)
15
+ Ethon::Easy.should_receive(:set_option).never
16
+ easy
17
+ end
18
+ end
19
+
20
+ context "when options not empty" do
21
+ context "when followlocation is set" do
22
+ let(:options) { { :followlocation => true } }
23
+ let(:easy) { Ethon::Easy.new(options) }
24
+
25
+ it "sets followlocation" do
26
+ Ethon::Easy.any_instance.should_receive(:set_callbacks)
27
+ Ethon::Curl.should_receive(:set_option).with do |option, value, _|
28
+ expect(option).to be(:followlocation)
29
+ expect(value).to be(true)
30
+ end
31
+ easy
32
+ end
33
+ end
34
+ end
35
+ end
36
+
37
+ describe "#set_attributes" do
38
+ context "when options are empty" do
39
+ it "sets only callbacks" do
40
+ Ethon::Easy.any_instance.should_receive(:set_callbacks)
41
+ Ethon::Easy.should_receive(:set_option).never
42
+ easy
43
+ end
44
+ end
45
+
46
+ context "when options aren't empty" do
47
+ context "when valid key" do
48
+ it "sets" do
49
+ easy.should_receive(:verbose=).with(true)
50
+ easy.set_attributes({:verbose => true})
51
+ end
52
+ end
53
+
54
+ context "when invalid key" do
55
+ it "raises invalid option error" do
56
+ expect{ easy.set_attributes({:fubar => 1}) }.to raise_error(Ethon::Errors::InvalidOption)
57
+ end
58
+ end
59
+ end
60
+ end
61
+
62
+ describe "#reset" do
63
+ before { easy.url = "www.example.com" }
64
+
65
+ it "resets url" do
66
+ easy.reset
67
+ expect(easy.url).to be_nil
68
+ end
69
+
70
+ it "resets hash" do
71
+ easy.reset
72
+ expect(easy.instance_variable_get(:@hash)).to be_nil
73
+ end
74
+
75
+ it "resets easy handle" do
76
+ Ethon::Curl.should_receive(:easy_reset)
77
+ easy.reset
78
+ end
79
+
80
+ it "resets on_complete" do
81
+ easy.on_complete { p 1 }
82
+ easy.reset
83
+ expect(easy.on_complete).to be_empty
84
+ end
85
+ end
86
+
87
+ describe "#mirror" do
88
+ it "returns a Mirror" do
89
+ expect(easy.mirror).to be_a(Ethon::Easy::Mirror)
90
+ end
91
+
92
+ it "builds from easy" do
93
+ Ethon::Easy::Mirror.should_receive(:from_easy).with(easy)
94
+ easy.mirror
95
+ end
96
+ end
97
+
98
+ describe "#log_inspect" do
99
+ [ :url, :response_code, :return_code, :total_time ].each do |name|
100
+ it "contains #{name}" do
101
+ expect(easy.log_inspect).to match name.to_s
102
+ end
103
+ end
104
+ end
105
+ end