241 lines
6.4 KiB
Ruby
241 lines
6.4 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
require "spec_helper"
|
|
require "timeout"
|
|
|
|
RSpec.describe NIO::Selector do
|
|
let(:pair) { IO.pipe }
|
|
let(:reader) { pair.first }
|
|
let(:writer) { pair.last }
|
|
|
|
context ".backends" do
|
|
it "knows all supported backends" do
|
|
expect(described_class.backends).to be_a Array
|
|
expect(described_class.backends.first).to be_a Symbol
|
|
end
|
|
end
|
|
|
|
context "#initialize" do
|
|
it "allows explicitly specifying a backend" do |example|
|
|
backend = described_class.backends.first
|
|
selector = described_class.new(backend)
|
|
expect(selector.backend).to eq backend
|
|
|
|
example.reporter.message "Supported backends: #{described_class.backends}"
|
|
end
|
|
|
|
it "automatically selects a backend if none or nil is specified" do
|
|
expect(described_class.new.backend).to eq described_class.new(nil).backend
|
|
end
|
|
|
|
it "raises ArgumentError if given an invalid backend" do
|
|
expect { described_class.new(:derp) }.to raise_error ArgumentError
|
|
end
|
|
|
|
it "raises TypeError if given a non-Symbol parameter" do
|
|
expect { described_class.new(42).to raise_error TypeError }
|
|
end
|
|
end
|
|
|
|
context "backend" do
|
|
it "knows its backend" do |example|
|
|
expect(subject.backend).to be_a Symbol
|
|
|
|
example.reporter.message "Current backend: #{subject.backend}"
|
|
end
|
|
end
|
|
|
|
context "register" do
|
|
it "registers IO objects" do
|
|
monitor = subject.register(reader, :r)
|
|
expect(monitor).not_to be_closed
|
|
end
|
|
|
|
it "raises TypeError if asked to register non-IO objects" do
|
|
expect { subject.register(42, :r) }.to raise_exception TypeError
|
|
end
|
|
|
|
it "raises when asked to register after closing" do
|
|
subject.close
|
|
expect { subject.register(reader, :r) }.to raise_exception IOError
|
|
end
|
|
end
|
|
|
|
it "knows which IO objects are registered" do
|
|
subject.register(reader, :r)
|
|
expect(subject).to be_registered(reader)
|
|
expect(subject).not_to be_registered(writer)
|
|
end
|
|
|
|
it "deregisters IO objects" do
|
|
subject.register(reader, :r)
|
|
|
|
monitor = subject.deregister(reader)
|
|
expect(subject).not_to be_registered(reader)
|
|
expect(monitor).to be_closed
|
|
end
|
|
|
|
it "allows deregistering closed IO objects" do
|
|
subject.register(reader, :r)
|
|
reader.close
|
|
|
|
expect do
|
|
subject.deregister(reader)
|
|
end.not_to raise_error
|
|
end
|
|
|
|
it "reports if it is empty" do
|
|
expect(subject).to be_empty
|
|
subject.register(reader, :r)
|
|
expect(subject).not_to be_empty
|
|
end
|
|
|
|
# This spec might seem a bit silly, but this actually something the
|
|
# Java NIO API specifically precludes that we need to work around
|
|
it "allows reregistration of the same IO object across select calls" do
|
|
monitor = subject.register(reader, :r)
|
|
writer << "ohai"
|
|
|
|
expect(subject.select).to include monitor
|
|
expect(reader.read(4)).to eq("ohai")
|
|
subject.deregister(reader)
|
|
|
|
new_monitor = subject.register(reader, :r)
|
|
writer << "thar"
|
|
expect(subject.select).to include new_monitor
|
|
expect(reader.read(4)).to eq("thar")
|
|
end
|
|
|
|
context "timeouts" do
|
|
let(:select_precision) {0.2}
|
|
let(:timeout) {2.0}
|
|
let(:payload) {"hi there"}
|
|
|
|
it "waits for timeout when selecting from empty selector" do
|
|
started_at = Time.now
|
|
expect(subject.select(timeout)).to be_nil
|
|
expect(Time.now - started_at).to be_within(select_precision).of(timeout)
|
|
end
|
|
|
|
it "waits for a timeout when selecting with reader" do
|
|
monitor = subject.register(reader, :r)
|
|
|
|
writer << payload
|
|
|
|
started_at = Time.now
|
|
expect(subject.select(timeout)).to include monitor
|
|
expect(Time.now - started_at).to be_within(select_precision).of(0)
|
|
reader.read_nonblock(payload.size)
|
|
|
|
started_at = Time.now
|
|
expect(subject.select(timeout)).to be_nil
|
|
expect(Time.now - started_at).to be_within(select_precision).of(timeout)
|
|
end
|
|
|
|
it "raises ArgumentError if given a negative timeout" do
|
|
subject.register(reader, :r)
|
|
|
|
expect do
|
|
subject.select(-1)
|
|
end.to raise_exception(ArgumentError)
|
|
end
|
|
end
|
|
|
|
context "wakeup" do
|
|
let(:select_precision) {0.2}
|
|
|
|
it "wakes up if signaled to from another thread" do
|
|
subject.register(reader, :r)
|
|
|
|
thread = Thread.new do
|
|
started_at = Time.now
|
|
expect(subject.select).to eq []
|
|
Time.now - started_at
|
|
end
|
|
|
|
timeout = 0.1
|
|
sleep timeout
|
|
subject.wakeup
|
|
|
|
expect(thread.value).to be_within(select_precision).of(timeout)
|
|
end
|
|
|
|
it "raises IOError if asked to wake up a closed selector" do
|
|
subject.close
|
|
expect(subject).to be_closed
|
|
|
|
expect { subject.wakeup }.to raise_exception IOError
|
|
end
|
|
end
|
|
|
|
context "select" do
|
|
it "does not block on super small precision intervals" do
|
|
wait_interval = 1e-4
|
|
|
|
expect do
|
|
Timeout.timeout(2) do
|
|
subject.select(wait_interval)
|
|
end
|
|
end.not_to raise_error
|
|
end
|
|
|
|
it "selects IO objects" do
|
|
writer << "ohai"
|
|
unready = IO.pipe.first
|
|
|
|
reader_monitor = subject.register(reader, :r)
|
|
unready_monitor = subject.register(unready, :r)
|
|
|
|
selected = subject.select(0)
|
|
expect(selected.size).to eq(1)
|
|
expect(selected).to include reader_monitor
|
|
expect(selected).not_to include unready_monitor
|
|
end
|
|
|
|
it "selects closed IO objects" do
|
|
monitor = subject.register(reader, :r)
|
|
expect(subject.select(0)).to be_nil
|
|
|
|
thread = Thread.new { subject.select }
|
|
Thread.pass while thread.status && thread.status != "sleep"
|
|
|
|
writer.close
|
|
selected = thread.value
|
|
expect(selected).to include monitor
|
|
end
|
|
|
|
it "iterates across selected objects with a block" do
|
|
readable1, writer = IO.pipe
|
|
writer << "ohai"
|
|
|
|
readable2, writer = IO.pipe
|
|
writer << "ohai"
|
|
|
|
unreadable = IO.pipe.first
|
|
|
|
monitor1 = subject.register(readable1, :r)
|
|
monitor2 = subject.register(readable2, :r)
|
|
monitor3 = subject.register(unreadable, :r)
|
|
|
|
readables = []
|
|
result = subject.select { |monitor| readables << monitor }
|
|
expect(result).to eq(2)
|
|
|
|
expect(readables).to include monitor1
|
|
expect(readables).to include monitor2
|
|
expect(readables).not_to include monitor3
|
|
end
|
|
|
|
it "raises IOError if asked to select on a closed selector" do
|
|
subject.close
|
|
|
|
expect { subject.select(0) }.to raise_exception IOError
|
|
end
|
|
end
|
|
|
|
it "closes" do
|
|
subject.close
|
|
expect(subject).to be_closed
|
|
end
|
|
end
|