aboutsummaryrefslogtreecommitdiffstats
path: root/actionpack/test/controller/live_stream_test.rb
diff options
context:
space:
mode:
Diffstat (limited to 'actionpack/test/controller/live_stream_test.rb')
-rw-r--r--actionpack/test/controller/live_stream_test.rb188
1 files changed, 172 insertions, 16 deletions
diff --git a/actionpack/test/controller/live_stream_test.rb b/actionpack/test/controller/live_stream_test.rb
index 0a431270b5..7fd1276e98 100644
--- a/actionpack/test/controller/live_stream_test.rb
+++ b/actionpack/test/controller/live_stream_test.rb
@@ -1,5 +1,6 @@
require 'abstract_unit'
require 'active_support/concurrency/latch'
+Thread.abort_on_exception = true
module ActionController
class SSETest < ActionController::TestCase
@@ -38,14 +39,19 @@ module ActionController
ensure
sse.close
end
+
+ def sse_with_multiple_line_message
+ sse = SSE.new(response.stream)
+ sse.write("first line.\nsecond line.")
+ ensure
+ sse.close
+ end
end
tests SSETestController
def wait_for_response_stream_close
- while !response.stream.closed?
- sleep 0.01
- end
+ response.body
end
def test_basic_sse
@@ -88,9 +94,21 @@ module ActionController
assert_match(/data: {\"name\":\"Ryan\"}/, second_response)
assert_match(/id: 2/, second_response)
end
+
+ def test_sse_with_multiple_line_message
+ get :sse_with_multiple_line_message
+
+ wait_for_response_stream_close
+ first_response, second_response = response.body.split("\n")
+ assert_match(/data: first line/, first_response)
+ assert_match(/data: second line/, second_response)
+ end
end
class LiveStreamTest < ActionController::TestCase
+ class Exception < StandardError
+ end
+
class TestController < ActionController::Base
include ActionController::Live
@@ -100,6 +118,12 @@ module ActionController
'test'
end
+ def set_cookie
+ cookies[:hello] = "world"
+ response.stream.write "hello world"
+ response.close
+ end
+
def render_text
render :text => 'zomg'
end
@@ -138,13 +162,18 @@ module ActionController
end
def with_stale
- render :text => 'stale' if stale?(:etag => "123")
+ render text: 'stale' if stale?(etag: "123", template: false)
end
def exception_in_view
render 'doesntexist'
end
+ def exception_in_view_after_commit
+ response.stream.write ""
+ render 'doesntexist'
+ end
+
def exception_with_callback
response.headers['Content-Type'] = 'text/event-stream'
@@ -153,32 +182,66 @@ module ActionController
response.stream.close
end
+ response.stream.write "" # make sure the response is committed
raise 'An exception occurred...'
end
+ def exception_in_controller
+ raise Exception, 'Exception in controller'
+ end
+
+ def bad_request_error
+ raise ActionController::BadRequest
+ end
+
def exception_in_exception_callback
response.headers['Content-Type'] = 'text/event-stream'
response.stream.on_error do
raise 'We need to go deeper.'
end
+ response.stream.write ''
response.stream.write params[:widget][:didnt_check_for_nil]
end
- end
- tests TestController
+ def overfill_buffer_and_die
+ # Write until the buffer is full. It doesn't expose that
+ # information directly, so we must hard-code its size:
+ 10.times do
+ response.stream.write '.'
+ end
+ # .. plus one more, because the #each frees up a slot:
+ response.stream.write '.'
+
+ latch.release
- class TestResponse < Live::Response
- def recycle!
- initialize
+ # This write will block, and eventually raise
+ response.stream.write 'x'
+
+ 20.times do
+ response.stream.write '.'
+ end
end
- end
- def build_response
- TestResponse.new
+ def ignore_client_disconnect
+ response.stream.ignore_disconnect = true
+
+ response.stream.write '' # commit
+
+ # These writes will be ignored
+ 15.times do
+ response.stream.write 'x'
+ end
+
+ logger.info 'Work complete'
+ latch.release
+ end
end
+ tests TestController
+
def assert_stream_closed
assert response.stream.closed?, 'stream should be closed'
+ assert response.sent?, 'stream should be sent'
end
def capture_log_output
@@ -192,6 +255,13 @@ module ActionController
end
end
+ def test_set_cookie
+ @controller = TestController.new
+ get :set_cookie
+ assert_equal({'hello' => 'world'}, @response.cookies)
+ assert_equal "hello world", @response.body
+ end
+
def test_set_response!
@controller.set_response!(@request)
assert_kind_of(Live::Response, @controller.response)
@@ -213,6 +283,7 @@ module ActionController
@controller.response = @response
t = Thread.new(@response) { |resp|
+ resp.await_commit
resp.stream.each do |part|
assert_equal parts.shift, part
ol = @controller.latch
@@ -226,6 +297,62 @@ module ActionController
assert t.join(3), 'timeout expired before the thread terminated'
end
+ def test_abort_with_full_buffer
+ @controller.latch = ActiveSupport::Concurrency::Latch.new
+
+ @request.parameters[:format] = 'plain'
+ @controller.request = @request
+ @controller.response = @response
+
+ got_error = ActiveSupport::Concurrency::Latch.new
+ @response.stream.on_error do
+ ActionController::Base.logger.warn 'Error while streaming'
+ got_error.release
+ end
+
+ t = Thread.new(@response) { |resp|
+ resp.await_commit
+ _, _, body = resp.to_a
+ body.each do |part|
+ @controller.latch.await
+ body.close
+ break
+ end
+ }
+
+ capture_log_output do |output|
+ @controller.process :overfill_buffer_and_die
+ t.join
+ got_error.await
+ assert_match 'Error while streaming', output.rewind && output.read
+ end
+ end
+
+ def test_ignore_client_disconnect
+ @controller.latch = ActiveSupport::Concurrency::Latch.new
+
+ @controller.request = @request
+ @controller.response = @response
+
+ t = Thread.new(@response) { |resp|
+ resp.await_commit
+ _, _, body = resp.to_a
+ body.each do |part|
+ body.close
+ break
+ end
+ }
+
+ capture_log_output do |output|
+ @controller.process :ignore_client_disconnect
+ t.join
+ Timeout.timeout(3) do
+ @controller.latch.await
+ end
+ assert_match 'Work complete', output.rewind && output.read
+ end
+ end
+
def test_thread_locals_get_copied
@controller.tc = self
Thread.current[:originating_thread] = Thread.current.object_id
@@ -249,24 +376,34 @@ module ActionController
end
def test_exception_handling_html
- capture_log_output do |output|
+ assert_raises(ActionView::MissingTemplate) do
get :exception_in_view
+ end
+
+ capture_log_output do |output|
+ get :exception_in_view_after_commit
assert_match %r((window\.location = "/500\.html"</script></html>)$), response.body
assert_match 'Missing template test/doesntexist', output.rewind && output.read
assert_stream_closed
end
+ assert response.body
+ assert_stream_closed
end
def test_exception_handling_plain_text
- capture_log_output do |output|
+ assert_raises(ActionView::MissingTemplate) do
get :exception_in_view, format: :json
+ end
+
+ capture_log_output do |output|
+ get :exception_in_view_after_commit, format: :json
assert_equal '', response.body
assert_match 'Missing template test/doesntexist', output.rewind && output.read
assert_stream_closed
end
end
- def test_exception_callback
+ def test_exception_callback_when_committed
capture_log_output do |output|
get :exception_with_callback, format: 'text/event-stream'
assert_equal %(data: "500 Internal Server Error"\n\n), response.body
@@ -275,7 +412,19 @@ module ActionController
end
end
- def test_exceptions_raised_handling_exceptions
+ def test_exception_in_controller_before_streaming
+ assert_raises(ActionController::LiveStreamTest::Exception) do
+ get :exception_in_controller, format: 'text/event-stream'
+ end
+ end
+
+ def test_bad_request_in_controller_before_streaming
+ assert_raises(ActionController::BadRequest) do
+ get :bad_request_error, format: 'text/event-stream'
+ end
+ end
+
+ def test_exceptions_raised_handling_exceptions_and_committed
capture_log_output do |output|
get :exception_in_exception_callback, format: 'text/event-stream'
assert_equal '', response.body
@@ -295,4 +444,11 @@ module ActionController
assert_equal 304, @response.status.to_i
end
end
+
+ class BufferTest < ActionController::TestCase
+ def test_nil_callback
+ buf = ActionController::Live::Buffer.new nil
+ assert buf.call_on_error
+ end
+ end
end