153 lines
4.2 KiB
Ruby
153 lines
4.2 KiB
Ruby
# frozen_string_literal: true
|
|
require 'spec_helper'
|
|
|
|
describe Ethon::Multi do
|
|
describe ".new" do
|
|
it "inits curl" do
|
|
expect(Ethon::Curl).to receive(:init)
|
|
Ethon::Multi.new
|
|
end
|
|
|
|
context "with default options" do
|
|
it "allows running #perform with the default execution_mode" do
|
|
Ethon::Multi.new.perform
|
|
end
|
|
|
|
it "refuses to run #socket_action" do
|
|
expect { Ethon::Multi.new.socket_action }.to raise_error(ArgumentError)
|
|
end
|
|
end
|
|
|
|
context "when options not empty" do
|
|
context "when pipelining is set" do
|
|
let(:options) { { :pipelining => true } }
|
|
|
|
it "sets pipelining" do
|
|
expect_any_instance_of(Ethon::Multi).to receive(:pipelining=).with(true)
|
|
Ethon::Multi.new(options)
|
|
end
|
|
end
|
|
|
|
context "when execution_mode option is :socket_action" do
|
|
let(:options) { { :execution_mode => :socket_action } }
|
|
let(:multi) { Ethon::Multi.new(options) }
|
|
|
|
it "refuses to run #perform" do
|
|
expect { multi.perform }.to raise_error(ArgumentError)
|
|
end
|
|
|
|
it "allows running #socket_action" do
|
|
multi.socket_action
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "#socket_action" do
|
|
let(:options) { { :execution_mode => :socket_action } }
|
|
let(:select_state) { { :readers => [], :writers => [], :timeout => 0 } }
|
|
let(:multi) {
|
|
multi = Ethon::Multi.new(options)
|
|
multi.timerfunction = proc do |handle, timeout_ms, userp|
|
|
timeout_ms = nil if timeout_ms == -1
|
|
select_state[:timeout] = timeout_ms
|
|
:ok
|
|
end
|
|
multi.socketfunction = proc do |handle, sock, what, userp, socketp|
|
|
case what
|
|
when :remove
|
|
select_state[:readers].delete(sock)
|
|
select_state[:writers].delete(sock)
|
|
when :in
|
|
select_state[:readers].push(sock) unless select_state[:readers].include? sock
|
|
select_state[:writers].delete(sock)
|
|
when :out
|
|
select_state[:readers].delete(sock)
|
|
select_state[:writers].push(sock) unless select_state[:writers].include? sock
|
|
when :inout
|
|
select_state[:readers].push(sock) unless select_state[:readers].include? sock
|
|
select_state[:writers].push(sock) unless select_state[:writers].include? sock
|
|
else
|
|
raise ArgumentError, "invalid value for 'what' in socketfunction callback"
|
|
end
|
|
:ok
|
|
end
|
|
multi
|
|
}
|
|
|
|
def fds_to_ios(fds)
|
|
fds.map do |fd|
|
|
IO.for_fd(fd).tap { |io| io.autoclose = false }
|
|
end
|
|
end
|
|
|
|
def perform_socket_action_until_complete
|
|
multi.socket_action # start things off
|
|
|
|
while multi.ongoing?
|
|
readers, writers, _ = IO.select(
|
|
fds_to_ios(select_state[:readers]),
|
|
fds_to_ios(select_state[:writers]),
|
|
[],
|
|
select_state[:timeout]
|
|
)
|
|
|
|
to_notify = Hash.new { |hash, key| hash[key] = [] }
|
|
unless readers.nil?
|
|
readers.each do |reader|
|
|
to_notify[reader] << :in
|
|
end
|
|
end
|
|
unless writers.nil?
|
|
writers.each do |writer|
|
|
to_notify[writer] << :out
|
|
end
|
|
end
|
|
|
|
to_notify.each do |io, readiness|
|
|
multi.socket_action(io, readiness)
|
|
end
|
|
|
|
# if we didn't have anything to notify, then we timed out
|
|
multi.socket_action if to_notify.empty?
|
|
end
|
|
ensure
|
|
multi.easy_handles.dup.each do |h|
|
|
multi.delete(h)
|
|
end
|
|
end
|
|
|
|
it "supports an end-to-end request" do
|
|
easy = Ethon::Easy.new
|
|
easy.url = "http://localhost:3001/"
|
|
multi.add(easy)
|
|
|
|
perform_socket_action_until_complete
|
|
|
|
expect(multi.ongoing?).to eq(false)
|
|
end
|
|
|
|
it "supports multiple concurrent requests" do
|
|
handles = []
|
|
10.times do
|
|
easy = Ethon::Easy.new
|
|
easy.url = "http://localhost:3001/?delay=1"
|
|
multi.add(easy)
|
|
handles << easy
|
|
end
|
|
|
|
start = Time.now
|
|
perform_socket_action_until_complete
|
|
duration = Time.now - start
|
|
|
|
# these should have happened concurrently
|
|
expect(duration).to be < 2
|
|
expect(multi.ongoing?).to eq(false)
|
|
|
|
handles.each do |handle|
|
|
expect(handle.response_code).to eq(200)
|
|
end
|
|
end
|
|
end
|
|
end
|