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
@@ -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