1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121
|
# A fake Excon::Socket that allows passing in an arbitrary backend @socket
class MockExconSocket < Excon::Socket
attr_reader :read_count
def initialize(backend_socket, *args)
super(*args)
@read_count = 0
@socket = backend_socket
end
def read_nonblock(*args)
@read_count += 1
super
end
def connect
# pass
end
def select_with_timeout(*args)
# don't actually wait, assume we're ready
end
end
# A socket whose read_nonblock returns from an input list,
# and which counts the number of reads
class MockNonblockRubySocket
attr_reader :sequence
def initialize(nonblock_reads)
@nonblock_reads = nonblock_reads
@sequence = []
end
def read_nonblock(maxlen, buffer = nil)
if @nonblock_reads.empty?
@sequence << 'EOF'
raise EOFError
elsif @nonblock_reads.first.empty?
@nonblock_reads.shift
if @nonblock_reads.empty?
@sequence << 'EOF'
raise EOFError
end
@sequence << 'EAGAIN'
raise Errno::EAGAIN
elsif
len = maxlen ? maxlen : @nonblock_reads.first.length
ret = @nonblock_reads.first.slice!(0, len)
@sequence << ret.length
if buffer
buffer.clear
buffer << ret
buffer
else
ret
end
end
end
# Returns the results of `block`, as well as how many times we called read on the Excon
# socket, and the sequence of reads on the backend socket
def self.check_reads(nonblock_reads, socket_args, &block)
backend_socket = MockNonblockRubySocket.new(nonblock_reads)
socket = MockExconSocket.new(backend_socket, { nonblock: true }.merge(socket_args))
ret = block[socket]
[ret, socket.read_count, backend_socket.sequence]
end
end
Shindo.tests('socket') do
CHUNK_SIZES = [nil, 512]
CHUNK_SIZES.each do |chunk_size|
tests("chunk_size: #{chunk_size}") do
socket_args = {chunk_size: chunk_size}
tests('read_nonblock') do
tests('readline nonblock is efficient') do
returns(["one\n", 1, [8, 'EOF']]) do
MockNonblockRubySocket.check_reads(["one\ntwo\n"], socket_args) do |sock|
sock.readline
end
end
end
tests('readline nonblock works sequentially') do
returns([["one\n", "two\n"], 1, [8, 'EOF']]) do
MockNonblockRubySocket.check_reads(["one\ntwo\n"], socket_args) do |sock|
2.times.map { sock.readline }
end
end
end
tests('readline nonblock can handle partial reads') do
returns([["one\n", "two\n"], 2, [5, 'EAGAIN', 3, 'EOF']]) do
MockNonblockRubySocket.check_reads(["one\nt", "wo\n"], socket_args) do |sock|
2.times.map { sock.readline }
end
end
end
tests('readline nonblock before read') do
returns([["one\n", "two\n"], 2, [8, 'EOF']]) do
MockNonblockRubySocket.check_reads(["one\ntwo\n"], socket_args) do |sock|
[sock.readline, sock.read(6)]
end
end
end
tests('read_nonblock does not EOF early') do
returns([["one", "two"], 2, [3, 'EAGAIN', 3, 'EOF']]) do
# Data, EAGAIN, data, EOF
MockNonblockRubySocket.check_reads(["one", "two"], socket_args) do |sock|
[sock.read, sock.read]
end
end
end
end
end
end
end
|