2014-05-09 02:39:16 -04:00
|
|
|
require 'spec_helper'
|
2011-08-18 03:21:40 -04:00
|
|
|
|
2022-02-05 07:27:48 -05:00
|
|
|
RSpec.describe Sinatra::Streaming do
|
2011-08-18 03:21:40 -04:00
|
|
|
def stream(&block)
|
|
|
|
rack_middleware = @use
|
|
|
|
out = nil
|
|
|
|
mock_app do
|
|
|
|
rack_middleware.each { |args| use(*args) }
|
|
|
|
helpers Sinatra::Streaming
|
|
|
|
get('/') { out = stream(&block) }
|
|
|
|
end
|
|
|
|
get('/')
|
|
|
|
out
|
|
|
|
end
|
|
|
|
|
|
|
|
def use(*args)
|
|
|
|
@use << args
|
|
|
|
end
|
|
|
|
|
|
|
|
before do
|
|
|
|
@use = []
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'stream test helper' do
|
|
|
|
it 'runs the given block' do
|
|
|
|
ran = false
|
|
|
|
stream { ran = true }
|
2016-05-10 10:08:54 -04:00
|
|
|
expect(ran).to be true
|
2011-08-18 03:21:40 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
it 'returns the stream object' do
|
|
|
|
out = stream { }
|
2016-05-10 10:08:54 -04:00
|
|
|
expect(out).to be_a(Sinatra::Helpers::Stream)
|
2011-08-18 03:21:40 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
it 'fires a request against that stream' do
|
|
|
|
stream { |out| out << "Hello World!" }
|
2016-05-10 10:08:54 -04:00
|
|
|
expect(last_response).to be_ok
|
|
|
|
expect(body).to eq("Hello World!")
|
2011-08-18 03:21:40 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
it 'passes the stream object to the block' do
|
|
|
|
passed = nil
|
|
|
|
returned = stream { |out| passed = out }
|
2016-05-10 10:08:54 -04:00
|
|
|
expect(passed).to eq(returned)
|
2011-08-18 03:21:40 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context Sinatra::Streaming::Stream do
|
|
|
|
it 'should extend the stream object' do
|
|
|
|
out = stream { }
|
2016-05-10 10:08:54 -04:00
|
|
|
expect(out).to be_a(Sinatra::Streaming::Stream)
|
2011-08-18 03:21:40 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
it 'should not extend stream objects of other apps' do
|
|
|
|
out = nil
|
|
|
|
mock_app { get('/') { out = stream { }}}
|
|
|
|
get('/')
|
2016-05-10 10:08:54 -04:00
|
|
|
expect(out).to be_a(Sinatra::Helpers::Stream)
|
|
|
|
expect(out).not_to be_a(Sinatra::Streaming::Stream)
|
2011-08-18 03:21:40 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'app' do
|
|
|
|
it 'is the app instance the stream was created from' do
|
|
|
|
out = stream { }
|
2016-05-10 10:08:54 -04:00
|
|
|
expect(out.app).to be_a(Sinatra::Base)
|
2011-08-18 03:21:40 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'lineno' do
|
|
|
|
it 'defaults to 0' do
|
2016-05-10 10:08:54 -04:00
|
|
|
expect(stream { }.lineno).to eq(0)
|
2011-08-18 03:21:40 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
it 'does not increase on write' do
|
|
|
|
stream do |out|
|
|
|
|
out << "many\nlines\n"
|
2016-05-10 10:08:54 -04:00
|
|
|
expect(out.lineno).to eq(0)
|
2011-08-18 03:21:40 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'is writable' do
|
|
|
|
out = stream { }
|
|
|
|
out.lineno = 10
|
2016-05-10 10:08:54 -04:00
|
|
|
expect(out.lineno).to eq(10)
|
2011-08-18 03:21:40 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'pos' do
|
|
|
|
it 'defaults to 0' do
|
2016-05-10 10:08:54 -04:00
|
|
|
expect(stream { }.pos).to eq(0)
|
2011-08-18 03:21:40 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
it 'increases when writing data' do
|
|
|
|
stream do |out|
|
2016-05-10 10:08:54 -04:00
|
|
|
expect(out.pos).to eq(0)
|
2011-08-18 03:21:40 -04:00
|
|
|
out << 'hi'
|
2016-05-10 10:08:54 -04:00
|
|
|
expect(out.pos).to eq(2)
|
2011-08-18 03:21:40 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'is writable' do
|
|
|
|
out = stream { }
|
|
|
|
out.pos = 10
|
2016-05-10 10:08:54 -04:00
|
|
|
expect(out.pos).to eq(10)
|
2011-08-18 03:21:40 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
it 'aliased to #tell' do
|
|
|
|
out = stream { }
|
2016-05-10 10:08:54 -04:00
|
|
|
expect(out.tell).to eq(0)
|
2011-08-18 03:21:40 -04:00
|
|
|
out.pos = 10
|
2016-05-10 10:08:54 -04:00
|
|
|
expect(out.tell).to eq(10)
|
2011-08-18 03:21:40 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'closed' do
|
|
|
|
it 'returns false while streaming' do
|
2016-05-10 10:08:54 -04:00
|
|
|
stream { |out| expect(out).not_to be_closed }
|
2011-08-18 03:21:40 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
it 'returns true after streaming' do
|
2016-05-10 10:08:54 -04:00
|
|
|
expect(stream {}).to be_closed
|
2011-08-18 03:21:40 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'map!' do
|
|
|
|
it 'applies transformations later' do
|
|
|
|
stream do |out|
|
|
|
|
out.map! { |s| s.upcase }
|
|
|
|
out << 'ok'
|
|
|
|
end
|
2016-05-10 10:08:54 -04:00
|
|
|
expect(body).to eq("OK")
|
2011-08-18 03:21:40 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
it 'is chainable' do
|
|
|
|
stream do |out|
|
|
|
|
out.map! { |s| s.upcase }
|
|
|
|
out.map! { |s| s.reverse }
|
|
|
|
out << 'ok'
|
|
|
|
end
|
2016-05-10 10:08:54 -04:00
|
|
|
expect(body).to eq("KO")
|
2011-08-18 03:21:40 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
it 'works with middleware' do
|
|
|
|
middleware = Class.new do
|
|
|
|
def initialize(app) @app = app end
|
|
|
|
def call(env)
|
|
|
|
status, headers, body = @app.call(env)
|
|
|
|
body.map! { |s| s.upcase }
|
|
|
|
[status, headers, body]
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
use middleware
|
|
|
|
stream { |out| out << "ok" }
|
2016-05-10 10:08:54 -04:00
|
|
|
expect(body).to eq("OK")
|
2011-08-18 03:21:40 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
it 'modifies each value separately' do
|
|
|
|
stream do |out|
|
|
|
|
out.map! { |s| s.reverse }
|
|
|
|
out << "ab" << "cd"
|
|
|
|
end
|
2016-05-10 10:08:54 -04:00
|
|
|
expect(body).to eq("badc")
|
2011-08-18 03:21:40 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'map' do
|
|
|
|
it 'works with middleware' do
|
|
|
|
middleware = Class.new do
|
|
|
|
def initialize(app) @app = app end
|
|
|
|
def call(env)
|
|
|
|
status, headers, body = @app.call(env)
|
|
|
|
[status, headers, body.map(&:upcase)]
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
use middleware
|
|
|
|
stream { |out| out << "ok" }
|
2016-05-10 10:08:54 -04:00
|
|
|
expect(body).to eq("OK")
|
2011-08-18 03:21:40 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
it 'is chainable' do
|
|
|
|
middleware = Class.new do
|
|
|
|
def initialize(app) @app = app end
|
|
|
|
def call(env)
|
|
|
|
status, headers, body = @app.call(env)
|
|
|
|
[status, headers, body.map(&:upcase).map(&:reverse)]
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
use middleware
|
|
|
|
stream { |out| out << "ok" }
|
2016-05-10 10:08:54 -04:00
|
|
|
expect(body).to eq("KO")
|
2011-08-18 03:21:40 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
it 'can be written as each.map' do
|
|
|
|
middleware = Class.new do
|
|
|
|
def initialize(app) @app = app end
|
|
|
|
def call(env)
|
|
|
|
status, headers, body = @app.call(env)
|
|
|
|
[status, headers, body.each.map(&:upcase)]
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
use middleware
|
|
|
|
stream { |out| out << "ok" }
|
2016-05-10 10:08:54 -04:00
|
|
|
expect(body).to eq("OK")
|
2011-08-18 03:21:40 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
it 'does not modify the original body' do
|
|
|
|
stream do |out|
|
|
|
|
out.map { |s| s.reverse }
|
|
|
|
out << 'ok'
|
|
|
|
end
|
2016-05-10 10:08:54 -04:00
|
|
|
expect(body).to eq('ok')
|
2011-08-18 03:21:40 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'write' do
|
|
|
|
it 'writes to the stream' do
|
|
|
|
stream { |out| out.write 'hi' }
|
2016-05-10 10:08:54 -04:00
|
|
|
expect(body).to eq('hi')
|
2011-08-18 03:21:40 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
it 'returns the number of bytes' do
|
|
|
|
stream do |out|
|
2016-05-10 10:08:54 -04:00
|
|
|
expect(out.write('hi')).to eq(2)
|
|
|
|
expect(out.write('hello')).to eq(5)
|
2011-08-18 03:21:40 -04:00
|
|
|
end
|
2011-08-18 06:06:34 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
it 'accepts non-string objects' do
|
|
|
|
stream do |out|
|
2016-05-10 10:08:54 -04:00
|
|
|
expect(out.write(12)).to eq(2)
|
2011-08-18 06:06:34 -04:00
|
|
|
end
|
2011-08-18 03:21:40 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
it 'should be aliased to syswrite' do
|
2016-05-10 10:08:54 -04:00
|
|
|
stream { |out| expect(out.syswrite('hi')).to eq(2) }
|
|
|
|
expect(body).to eq('hi')
|
2011-08-18 03:21:40 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
it 'should be aliased to write_nonblock' do
|
2016-05-10 10:08:54 -04:00
|
|
|
stream { |out| expect(out.write_nonblock('hi')).to eq(2) }
|
|
|
|
expect(body).to eq('hi')
|
2011-08-18 03:21:40 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'print' do
|
|
|
|
it 'writes to the stream' do
|
|
|
|
stream { |out| out.print('hi') }
|
2016-05-10 10:08:54 -04:00
|
|
|
expect(body).to eq('hi')
|
2011-08-18 03:21:40 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
it 'accepts multiple arguments' do
|
|
|
|
stream { |out| out.print(1, 2, 3, 4) }
|
2016-05-10 10:08:54 -04:00
|
|
|
expect(body).to eq('1234')
|
2011-08-18 03:21:40 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
it 'returns nil' do
|
2016-05-10 10:08:54 -04:00
|
|
|
stream { |out| expect(out.print('hi')).to be_nil }
|
2011-08-18 03:21:40 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'printf' do
|
|
|
|
it 'writes to the stream' do
|
|
|
|
stream { |out| out.printf('hi') }
|
2016-05-10 10:08:54 -04:00
|
|
|
expect(body).to eq('hi')
|
2011-08-18 03:21:40 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
it 'interpolates the format string' do
|
|
|
|
stream { |out| out.printf("%s: %d", "answer", 42) }
|
2016-05-10 10:08:54 -04:00
|
|
|
expect(body).to eq('answer: 42')
|
2011-08-18 03:21:40 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
it 'returns nil' do
|
2016-05-10 10:08:54 -04:00
|
|
|
stream { |out| expect(out.printf('hi')).to be_nil }
|
2011-08-18 03:21:40 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'putc' do
|
|
|
|
it 'writes the first character of a string' do
|
|
|
|
stream { |out| out.putc('hi') }
|
2016-05-10 10:08:54 -04:00
|
|
|
expect(body).to eq('h')
|
2011-08-18 03:21:40 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
it 'writes the character corresponding to an integer' do
|
|
|
|
stream { |out| out.putc(42) }
|
2016-05-10 10:08:54 -04:00
|
|
|
expect(body).to eq('*')
|
2011-08-18 03:21:40 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
it 'returns nil' do
|
2016-05-10 10:08:54 -04:00
|
|
|
stream { |out| expect(out.putc('hi')).to be_nil }
|
2011-08-18 03:21:40 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'puts' do
|
|
|
|
it 'writes to the stream' do
|
|
|
|
stream { |out| out.puts('hi') }
|
2016-05-10 10:08:54 -04:00
|
|
|
expect(body).to eq("hi\n")
|
2011-08-18 03:21:40 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
it 'accepts multiple arguments' do
|
|
|
|
stream { |out| out.puts(1, 2, 3, 4) }
|
2016-05-10 10:08:54 -04:00
|
|
|
expect(body).to eq("1\n2\n3\n4\n")
|
2011-08-18 03:21:40 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
it 'returns nil' do
|
2016-05-10 10:08:54 -04:00
|
|
|
stream { |out| expect(out.puts('hi')).to be_nil }
|
2011-08-18 03:21:40 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'close' do
|
|
|
|
it 'sets #closed? to true' do
|
|
|
|
stream do |out|
|
|
|
|
out.close
|
2016-05-10 10:08:54 -04:00
|
|
|
expect(out).to be_closed
|
2011-08-18 03:21:40 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'sets #closed_write? to true' do
|
|
|
|
stream do |out|
|
2016-05-10 10:08:54 -04:00
|
|
|
expect(out).not_to be_closed_write
|
2011-08-18 03:21:40 -04:00
|
|
|
out.close
|
2016-05-10 10:08:54 -04:00
|
|
|
expect(out).to be_closed_write
|
2011-08-18 03:21:40 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'fires callbacks' do
|
|
|
|
stream do |out|
|
|
|
|
fired = false
|
|
|
|
out.callback { fired = true }
|
|
|
|
out.close
|
2016-05-10 10:08:54 -04:00
|
|
|
expect(fired).to be true
|
2011-08-18 03:21:40 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'prevents from further writing' do
|
|
|
|
stream do |out|
|
|
|
|
out.close
|
|
|
|
expect { out << 'hi' }.to raise_error(IOError, 'not opened for writing')
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'close_read' do
|
|
|
|
it 'raises the appropriate exception' do
|
|
|
|
expect { stream { |out| out.close_read }}.
|
|
|
|
to raise_error(IOError, "closing non-duplex IO for reading")
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'closed_read?' do
|
2016-05-10 10:08:54 -04:00
|
|
|
it('returns true') { stream { |out| expect(out).to be_closed_read }}
|
2011-08-18 03:21:40 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
context 'rewind' do
|
|
|
|
it 'resets pos' do
|
|
|
|
stream do |out|
|
|
|
|
out << 'hi'
|
|
|
|
out.rewind
|
2016-05-10 10:08:54 -04:00
|
|
|
expect(out.pos).to eq(0)
|
2011-08-18 03:21:40 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'resets lineno' do
|
|
|
|
stream do |out|
|
|
|
|
out.lineno = 10
|
|
|
|
out.rewind
|
2016-05-10 10:08:54 -04:00
|
|
|
expect(out.lineno).to eq(0)
|
2011-08-18 03:21:40 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
raises = %w[
|
|
|
|
bytes eof? eof getbyte getc gets read read_nonblock readbyte readchar
|
|
|
|
readline readlines readpartial sysread ungetbyte ungetc
|
|
|
|
]
|
|
|
|
|
|
|
|
enum = %w[chars each_line each_byte each_char lines]
|
|
|
|
dummies = %w[flush fsync internal_encoding pid]
|
|
|
|
|
|
|
|
raises.each do |method|
|
|
|
|
context method do
|
|
|
|
it 'raises the appropriate exception' do
|
|
|
|
expect { stream { |out| out.public_send(method) }}.
|
|
|
|
to raise_error(IOError, "not opened for reading")
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
enum.each do |method|
|
|
|
|
context method do
|
|
|
|
it 'creates an Enumerator' do
|
2016-05-10 10:08:54 -04:00
|
|
|
stream { |out| expect(out.public_send(method)).to be_a(Enumerator) }
|
2011-08-18 03:21:40 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
it 'calling each raises the appropriate exception' do
|
|
|
|
expect { stream { |out| out.public_send(method).each { }}}.
|
|
|
|
to raise_error(IOError, "not opened for reading")
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
dummies.each do |method|
|
|
|
|
context method do
|
|
|
|
it 'returns nil' do
|
2016-05-10 10:08:54 -04:00
|
|
|
stream { |out| expect(out.public_send(method)).to be_nil }
|
2011-08-18 03:21:40 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|