ethon 0.11.0 → 0.15.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (103) hide show
  1. checksums.yaml +5 -5
  2. data/.github/workflows/ruby.yml +41 -0
  3. data/.gitignore +1 -0
  4. data/CHANGELOG.md +16 -1
  5. data/Gemfile +5 -0
  6. data/Guardfile +1 -0
  7. data/README.md +1 -1
  8. data/Rakefile +1 -0
  9. data/ethon.gemspec +2 -1
  10. data/lib/ethon/curl.rb +1 -0
  11. data/lib/ethon/curls/classes.rb +1 -0
  12. data/lib/ethon/curls/codes.rb +1 -1
  13. data/lib/ethon/curls/constants.rb +18 -0
  14. data/lib/ethon/curls/form_options.rb +1 -0
  15. data/lib/ethon/curls/functions.rb +5 -13
  16. data/lib/ethon/curls/infos.rb +4 -3
  17. data/lib/ethon/curls/messages.rb +1 -0
  18. data/lib/ethon/curls/options.rb +62 -25
  19. data/lib/ethon/curls/settings.rb +3 -0
  20. data/lib/ethon/easy/callbacks.rb +4 -3
  21. data/lib/ethon/easy/debug_info.rb +1 -0
  22. data/lib/ethon/easy/features.rb +1 -0
  23. data/lib/ethon/easy/form.rb +1 -0
  24. data/lib/ethon/easy/header.rb +1 -0
  25. data/lib/ethon/easy/http/actionable.rb +1 -0
  26. data/lib/ethon/easy/http/custom.rb +1 -0
  27. data/lib/ethon/easy/http/delete.rb +1 -0
  28. data/lib/ethon/easy/http/get.rb +1 -0
  29. data/lib/ethon/easy/http/head.rb +1 -0
  30. data/lib/ethon/easy/http/options.rb +1 -0
  31. data/lib/ethon/easy/http/patch.rb +1 -0
  32. data/lib/ethon/easy/http/post.rb +1 -0
  33. data/lib/ethon/easy/http/postable.rb +1 -0
  34. data/lib/ethon/easy/http/put.rb +1 -0
  35. data/lib/ethon/easy/http/putable.rb +1 -0
  36. data/lib/ethon/easy/http.rb +1 -0
  37. data/lib/ethon/easy/informations.rb +20 -1
  38. data/lib/ethon/easy/mirror.rb +1 -0
  39. data/lib/ethon/easy/operations.rb +1 -0
  40. data/lib/ethon/easy/options.rb +1 -0
  41. data/lib/ethon/easy/params.rb +1 -0
  42. data/lib/ethon/easy/queryable.rb +4 -1
  43. data/lib/ethon/easy/response_callbacks.rb +1 -0
  44. data/lib/ethon/easy/util.rb +1 -0
  45. data/lib/ethon/easy.rb +1 -1
  46. data/lib/ethon/errors/ethon_error.rb +1 -0
  47. data/lib/ethon/errors/global_init.rb +1 -0
  48. data/lib/ethon/errors/invalid_option.rb +1 -0
  49. data/lib/ethon/errors/invalid_value.rb +1 -0
  50. data/lib/ethon/errors/multi_add.rb +1 -0
  51. data/lib/ethon/errors/multi_fdset.rb +1 -0
  52. data/lib/ethon/errors/multi_remove.rb +1 -0
  53. data/lib/ethon/errors/multi_timeout.rb +1 -0
  54. data/lib/ethon/errors/select.rb +1 -0
  55. data/lib/ethon/errors.rb +1 -0
  56. data/lib/ethon/libc.rb +1 -0
  57. data/lib/ethon/loggable.rb +1 -0
  58. data/lib/ethon/multi/operations.rb +46 -8
  59. data/lib/ethon/multi/options.rb +9 -8
  60. data/lib/ethon/multi/stack.rb +1 -0
  61. data/lib/ethon/multi.rb +23 -0
  62. data/lib/ethon/version.rb +2 -1
  63. data/lib/ethon.rb +1 -0
  64. data/profile/benchmarks.rb +1 -0
  65. data/profile/memory_leaks.rb +1 -0
  66. data/profile/perf_spec_helper.rb +1 -0
  67. data/profile/support/memory_test_helpers.rb +1 -0
  68. data/profile/support/os_memory_leak_tracker.rb +1 -0
  69. data/profile/support/ruby_object_leak_tracker.rb +1 -0
  70. data/spec/ethon/curl_spec.rb +1 -0
  71. data/spec/ethon/easy/callbacks_spec.rb +1 -0
  72. data/spec/ethon/easy/debug_info_spec.rb +1 -0
  73. data/spec/ethon/easy/features_spec.rb +1 -0
  74. data/spec/ethon/easy/form_spec.rb +1 -0
  75. data/spec/ethon/easy/header_spec.rb +1 -0
  76. data/spec/ethon/easy/http/custom_spec.rb +1 -0
  77. data/spec/ethon/easy/http/delete_spec.rb +1 -0
  78. data/spec/ethon/easy/http/get_spec.rb +1 -0
  79. data/spec/ethon/easy/http/head_spec.rb +1 -0
  80. data/spec/ethon/easy/http/options_spec.rb +1 -0
  81. data/spec/ethon/easy/http/patch_spec.rb +1 -0
  82. data/spec/ethon/easy/http/post_spec.rb +1 -0
  83. data/spec/ethon/easy/http/put_spec.rb +1 -0
  84. data/spec/ethon/easy/http_spec.rb +1 -0
  85. data/spec/ethon/easy/informations_spec.rb +23 -0
  86. data/spec/ethon/easy/mirror_spec.rb +2 -0
  87. data/spec/ethon/easy/operations_spec.rb +1 -0
  88. data/spec/ethon/easy/options_spec.rb +2 -1
  89. data/spec/ethon/easy/queryable_spec.rb +9 -0
  90. data/spec/ethon/easy/response_callbacks_spec.rb +1 -0
  91. data/spec/ethon/easy/util_spec.rb +1 -0
  92. data/spec/ethon/easy_spec.rb +3 -2
  93. data/spec/ethon/libc_spec.rb +1 -0
  94. data/spec/ethon/loggable_spec.rb +1 -0
  95. data/spec/ethon/multi/operations_spec.rb +1 -0
  96. data/spec/ethon/multi/options_spec.rb +114 -0
  97. data/spec/ethon/multi/stack_spec.rb +1 -0
  98. data/spec/ethon/multi_spec.rb +131 -0
  99. data/spec/spec_helper.rb +1 -0
  100. data/spec/support/localhost_server.rb +2 -1
  101. data/spec/support/server.rb +1 -0
  102. metadata +6 -7
  103. data/.travis.yml +0 -28
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'spec_helper'
2
3
 
3
4
  describe Ethon::Multi::Options do
@@ -19,6 +20,119 @@ describe Ethon::Multi::Options do
19
20
  end
20
21
  end
21
22
 
23
+ context "socket_action mode" do
24
+ let(:multi) { Ethon::Multi.new(execution_mode: :socket_action) }
25
+
26
+ describe "#socketfunction callbacks" do
27
+ it "allows multi_code return values" do
28
+ calls = []
29
+ multi.socketfunction = proc do |handle, sock, what, userp, socketp|
30
+ calls << what
31
+ :ok
32
+ end
33
+
34
+ easy = Ethon::Easy.new
35
+ easy.url = "http://localhost:3001/?delay=1"
36
+ multi.add(easy)
37
+ expect(calls).to eq([])
38
+ 5.times do
39
+ multi.socket_action
40
+ break unless calls.empty?
41
+ sleep 0.1
42
+ end
43
+ expect(calls.last).to eq(:in).or(eq(:out))
44
+ multi.delete(easy)
45
+ expect(calls.last).to eq(:remove)
46
+ end
47
+
48
+ it "allows integer return values (compatibility)" do
49
+ called = false
50
+ multi.socketfunction = proc do |handle, sock, what, userp, socketp|
51
+ called = true
52
+ 0
53
+ end
54
+
55
+ easy = Ethon::Easy.new
56
+ easy.url = "http://localhost:3001/?delay=1"
57
+ multi.add(easy)
58
+ 5.times do
59
+ multi.socket_action
60
+ break if called
61
+ sleep 0.1
62
+ end
63
+ multi.delete(easy)
64
+
65
+ expect(called).to be_truthy
66
+ end
67
+
68
+ it "errors on invalid return codes" do
69
+ called = false
70
+ multi.socketfunction = proc do |handle, sock, what, userp, socketp|
71
+ called = true
72
+ "hi"
73
+ end
74
+
75
+ easy = Ethon::Easy.new
76
+ easy.url = "http://localhost:3001/?delay=1"
77
+ multi.add(easy)
78
+ expect {
79
+ 5.times do
80
+ multi.socket_action
81
+ break if called
82
+ sleep 0.1
83
+ end
84
+ }.to raise_error(ArgumentError)
85
+ expect { multi.delete(easy) }.to raise_error(ArgumentError)
86
+ end
87
+ end
88
+
89
+ describe "#timerfunction callbacks" do
90
+ it "allows multi_code return values" do
91
+ calls = []
92
+ multi.timerfunction = proc do |handle, timeout_ms, userp|
93
+ calls << timeout_ms
94
+ :ok
95
+ end
96
+
97
+ easy = Ethon::Easy.new
98
+ easy.url = "http://localhost:3001/?delay=1"
99
+ multi.add(easy)
100
+ expect(calls.last).to be >= 0 # adds an immediate timeout
101
+
102
+ multi.delete(easy)
103
+ expect(calls.last).to eq(-1) # cancels the timer
104
+ end
105
+
106
+ it "allows integer return values (compatibility)" do
107
+ called = false
108
+ multi.timerfunction = proc do |handle, timeout_ms, userp|
109
+ called = true
110
+ 0
111
+ end
112
+
113
+ easy = Ethon::Easy.new
114
+ easy.url = "http://localhost:3001/?delay=1"
115
+ multi.add(easy)
116
+ multi.socket_action
117
+ multi.delete(easy)
118
+
119
+ expect(called).to be_truthy
120
+ end
121
+
122
+ it "errors on invalid return codes" do
123
+ called = false
124
+ multi.timerfunction = proc do |handle, timeout_ms, userp|
125
+ called = true
126
+ "hi"
127
+ end
128
+
129
+ easy = Ethon::Easy.new
130
+ easy.url = "http://localhost:3001/?delay=1"
131
+ expect { multi.add(easy) }.to raise_error(ArgumentError)
132
+ end
133
+ end
134
+ end
135
+
22
136
  describe "#value_for" do
23
137
  context "when option in bool" do
24
138
  context "when value true" do
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'spec_helper'
2
3
 
3
4
  describe Ethon::Multi::Stack do
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'spec_helper'
2
3
 
3
4
  describe Ethon::Multi do
@@ -7,6 +8,16 @@ describe Ethon::Multi do
7
8
  Ethon::Multi.new
8
9
  end
9
10
 
11
+ context "with default options" do
12
+ it "allows running #perform with the default execution_mode" do
13
+ Ethon::Multi.new.perform
14
+ end
15
+
16
+ it "refuses to run #socket_action" do
17
+ expect { Ethon::Multi.new.socket_action }.to raise_error(ArgumentError)
18
+ end
19
+ end
20
+
10
21
  context "when options not empty" do
11
22
  context "when pipelining is set" do
12
23
  let(:options) { { :pipelining => true } }
@@ -16,6 +27,126 @@ describe Ethon::Multi do
16
27
  Ethon::Multi.new(options)
17
28
  end
18
29
  end
30
+
31
+ context "when execution_mode option is :socket_action" do
32
+ let(:options) { { :execution_mode => :socket_action } }
33
+ let(:multi) { Ethon::Multi.new(options) }
34
+
35
+ it "refuses to run #perform" do
36
+ expect { multi.perform }.to raise_error(ArgumentError)
37
+ end
38
+
39
+ it "allows running #socket_action" do
40
+ multi.socket_action
41
+ end
42
+ end
43
+ end
44
+ end
45
+
46
+ describe "#socket_action" do
47
+ let(:options) { { :execution_mode => :socket_action } }
48
+ let(:select_state) { { :readers => [], :writers => [], :timeout => 0 } }
49
+ let(:multi) {
50
+ multi = Ethon::Multi.new(options)
51
+ multi.timerfunction = proc do |handle, timeout_ms, userp|
52
+ timeout_ms = nil if timeout_ms == -1
53
+ select_state[:timeout] = timeout_ms
54
+ :ok
55
+ end
56
+ multi.socketfunction = proc do |handle, sock, what, userp, socketp|
57
+ case what
58
+ when :remove
59
+ select_state[:readers].delete(sock)
60
+ select_state[:writers].delete(sock)
61
+ when :in
62
+ select_state[:readers].push(sock) unless select_state[:readers].include? sock
63
+ select_state[:writers].delete(sock)
64
+ when :out
65
+ select_state[:readers].delete(sock)
66
+ select_state[:writers].push(sock) unless select_state[:writers].include? sock
67
+ when :inout
68
+ select_state[:readers].push(sock) unless select_state[:readers].include? sock
69
+ select_state[:writers].push(sock) unless select_state[:writers].include? sock
70
+ else
71
+ raise ArgumentError, "invalid value for 'what' in socketfunction callback"
72
+ end
73
+ :ok
74
+ end
75
+ multi
76
+ }
77
+
78
+ def fds_to_ios(fds)
79
+ fds.map do |fd|
80
+ IO.for_fd(fd).tap { |io| io.autoclose = false }
81
+ end
82
+ end
83
+
84
+ def perform_socket_action_until_complete
85
+ multi.socket_action # start things off
86
+
87
+ while multi.ongoing?
88
+ readers, writers, _ = IO.select(
89
+ fds_to_ios(select_state[:readers]),
90
+ fds_to_ios(select_state[:writers]),
91
+ [],
92
+ select_state[:timeout]
93
+ )
94
+
95
+ to_notify = Hash.new { |hash, key| hash[key] = [] }
96
+ unless readers.nil?
97
+ readers.each do |reader|
98
+ to_notify[reader] << :in
99
+ end
100
+ end
101
+ unless writers.nil?
102
+ writers.each do |writer|
103
+ to_notify[writer] << :out
104
+ end
105
+ end
106
+
107
+ to_notify.each do |io, readiness|
108
+ multi.socket_action(io, readiness)
109
+ end
110
+
111
+ # if we didn't have anything to notify, then we timed out
112
+ multi.socket_action if to_notify.empty?
113
+ end
114
+ ensure
115
+ multi.easy_handles.dup.each do |h|
116
+ multi.delete(h)
117
+ end
118
+ end
119
+
120
+ it "supports an end-to-end request" do
121
+ easy = Ethon::Easy.new
122
+ easy.url = "http://localhost:3001/"
123
+ multi.add(easy)
124
+
125
+ perform_socket_action_until_complete
126
+
127
+ expect(multi.ongoing?).to eq(false)
128
+ end
129
+
130
+ it "supports multiple concurrent requests" do
131
+ handles = []
132
+ 10.times do
133
+ easy = Ethon::Easy.new
134
+ easy.url = "http://localhost:3001/?delay=1"
135
+ multi.add(easy)
136
+ handles << easy
137
+ end
138
+
139
+ start = Time.now
140
+ perform_socket_action_until_complete
141
+ duration = Time.now - start
142
+
143
+ # these should have happened concurrently
144
+ expect(duration).to be < 2
145
+ expect(multi.ongoing?).to eq(false)
146
+
147
+ handles.each do |handle|
148
+ expect(handle.response_code).to eq(200)
149
+ end
19
150
  end
20
151
  end
21
152
  end
data/spec/spec_helper.rb CHANGED
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  $LOAD_PATH.unshift(File.dirname(__FILE__))
2
3
  $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), "..", "lib"))
3
4
 
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'rack'
2
3
  require 'rack/handler/webrick'
3
4
  require 'net/http'
@@ -43,7 +44,7 @@ class LocalhostServer
43
44
  # Use WEBrick since it's part of the ruby standard library and is available on all ruby interpreters.
44
45
  options = { :Port => port }
45
46
  options.merge!(:AccessLog => [], :Logger => WEBrick::BasicLog.new(StringIO.new)) unless ENV['VERBOSE_SERVER']
46
- Rack::Handler::WEBrick.run(Identify.new(@rack_app), options)
47
+ Rack::Handler::WEBrick.run(Identify.new(@rack_app), **options)
47
48
  end
48
49
 
49
50
  def booted?
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
  require 'json'
3
4
  require 'zlib'
4
5
  require 'sinatra/base'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ethon
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.11.0
4
+ version: 0.15.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Hans Hasselberg
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-10-26 00:00:00.000000000 Z
11
+ date: 2021-10-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ffi
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: 1.3.0
19
+ version: 1.15.0
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: 1.3.0
26
+ version: 1.15.0
27
27
  description: Very lightweight libcurl wrapper.
28
28
  email:
29
29
  - me@hans.io
@@ -31,9 +31,9 @@ executables: []
31
31
  extensions: []
32
32
  extra_rdoc_files: []
33
33
  files:
34
+ - ".github/workflows/ruby.yml"
34
35
  - ".gitignore"
35
36
  - ".rspec"
36
- - ".travis.yml"
37
37
  - CHANGELOG.md
38
38
  - Gemfile
39
39
  - Guardfile
@@ -152,8 +152,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
152
152
  - !ruby/object:Gem::Version
153
153
  version: 1.3.6
154
154
  requirements: []
155
- rubyforge_project:
156
- rubygems_version: 2.2.2
155
+ rubygems_version: 3.0.3
157
156
  signing_key:
158
157
  specification_version: 4
159
158
  summary: Libcurl wrapper.
data/.travis.yml DELETED
@@ -1,28 +0,0 @@
1
- language: ruby
2
- cache: bundler
3
- sudo: false
4
-
5
- bundler_args: --without perf
6
- script: bundle exec rake
7
- before_install:
8
- - gem install bundler
9
-
10
- rvm:
11
- - 1.8.7
12
- - 1.9.2
13
- - 1.9.3
14
- - 2.0.0-p648
15
- - 2.1.8
16
- - 2.2.4
17
- - 2.3.0
18
- - ruby-head
19
- - ree
20
- - jruby-18mode
21
- - jruby-19mode
22
- - jruby-head
23
-
24
- matrix:
25
- allow_failures:
26
- - rvm: ree
27
- - rvm: ruby-head
28
- - rvm: jruby-head