diff options
Diffstat (limited to 'actioncable/test')
21 files changed, 452 insertions, 87 deletions
diff --git a/actioncable/test/channel/periodic_timers_test.rb b/actioncable/test/channel/periodic_timers_test.rb index 1590a12f09..e6f0c14c9d 100644 --- a/actioncable/test/channel/periodic_timers_test.rb +++ b/actioncable/test/channel/periodic_timers_test.rb @@ -31,7 +31,7 @@ class ActionCable::Channel::PeriodicTimersTest < ActiveSupport::TestCase end test "timer start and stop" do - EventMachine::PeriodicTimer.expects(:new).times(2).returns(true) + @connection.server.event_loop.expects(:timer).times(2).returns(true) channel = ChatChannel.new @connection, "{id: 1}", { id: 1 } channel.expects(:stop_periodic_timers).once diff --git a/actioncable/test/channel/stream_test.rb b/actioncable/test/channel/stream_test.rb index 3fa2b291b7..526ea92e4f 100644 --- a/actioncable/test/channel/stream_test.rb +++ b/actioncable/test/channel/stream_test.rb @@ -14,7 +14,12 @@ class ActionCable::Channel::StreamTest < ActionCable::TestCase def send_confirmation transmit_subscription_confirmation end + end + class SymbolChannel < ActionCable::Channel::Base + def subscribed + stream_from :channel + end end test "streaming start and stop" do @@ -28,12 +33,21 @@ class ActionCable::Channel::StreamTest < ActionCable::TestCase end end + test "stream from non-string channel" do + run_in_eventmachine do + connection = TestConnection.new + connection.expects(:pubsub).returns mock().tap { |m| m.expects(:subscribe).with("channel", kind_of(Proc), kind_of(Proc)).returns stub_everything(:pubsub) } + channel = SymbolChannel.new connection, "" + + connection.expects(:pubsub).returns mock().tap { |m| m.expects(:unsubscribe) } + channel.unsubscribe_from_channel + end + end + test "stream_for" do run_in_eventmachine do connection = TestConnection.new - EM.next_tick do - connection.expects(:pubsub).returns mock().tap { |m| m.expects(:subscribe).with("action_cable:channel:stream_test:chat:Room#1-Campfire", kind_of(Proc), kind_of(Proc)).returns stub_everything(:pubsub) } - end + connection.expects(:pubsub).returns mock().tap { |m| m.expects(:subscribe).with("action_cable:channel:stream_test:chat:Room#1-Campfire", kind_of(Proc), kind_of(Proc)).returns stub_everything(:pubsub) } channel = ChatChannel.new connection, "" channel.stream_for Room.new(1) @@ -41,39 +55,35 @@ class ActionCable::Channel::StreamTest < ActionCable::TestCase end test "stream_from subscription confirmation" do - EM.run do + run_in_eventmachine do connection = TestConnection.new ChatChannel.new connection, "{id: 1}", { id: 1 } assert_nil connection.last_transmission - EM::Timer.new(0.1) do - expected = ActiveSupport::JSON.encode "identifier" => "{id: 1}", "type" => "confirm_subscription" - connection.transmit(expected) + wait_for_async - assert_equal expected, connection.last_transmission, "Did not receive subscription confirmation within 0.1s" + expected = ActiveSupport::JSON.encode "identifier" => "{id: 1}", "type" => "confirm_subscription" + connection.transmit(expected) - EM.run_deferred_callbacks - EM.stop - end + assert_equal expected, connection.last_transmission, "Did not receive subscription confirmation within 0.1s" end end test "subscription confirmation should only be sent out once" do - EM.run do + run_in_eventmachine do connection = TestConnection.new channel = ChatChannel.new connection, "test_channel" channel.send_confirmation channel.send_confirmation - EM.run_deferred_callbacks + wait_for_async expected = ActiveSupport::JSON.encode "identifier" => "test_channel", "type" => "confirm_subscription" assert_equal expected, connection.last_transmission, "Did not receive subscription confirmation" assert_equal 1, connection.transmissions.size - EM.stop end end diff --git a/actioncable/test/client/echo_channel.rb b/actioncable/test/client/echo_channel.rb new file mode 100644 index 0000000000..5a7bac25c5 --- /dev/null +++ b/actioncable/test/client/echo_channel.rb @@ -0,0 +1,22 @@ +class EchoChannel < ActionCable::Channel::Base + def subscribed + stream_from "global" + end + + def unsubscribed + 'Goodbye from EchoChannel!' + end + + def ding(data) + transmit(dong: data['message']) + end + + def delay(data) + sleep 1 + transmit(dong: data['message']) + end + + def bulk(data) + ActionCable.server.broadcast "global", wide: data['message'] + end +end diff --git a/actioncable/test/client_test.rb b/actioncable/test/client_test.rb new file mode 100644 index 0000000000..5f5c09d1a1 --- /dev/null +++ b/actioncable/test/client_test.rb @@ -0,0 +1,251 @@ +require 'test_helper' +require 'concurrent' + +require 'active_support/core_ext/hash/indifferent_access' +require 'pathname' + +require 'faye/websocket' +require 'json' + +class ClientTest < ActionCable::TestCase + WAIT_WHEN_EXPECTING_EVENT = 8 + WAIT_WHEN_NOT_EXPECTING_EVENT = 0.5 + + def setup + ActionCable.instance_variable_set(:@server, nil) + server = ActionCable.server + server.config.logger = Logger.new(StringIO.new).tap { |l| l.level = Logger::UNKNOWN } + + server.config.cable = { adapter: 'async' }.with_indifferent_access + server.config.use_faye = ENV['FAYE'].present? + + # and now the "real" setup for our test: + server.config.disable_request_forgery_protection = true + server.config.channel_paths = [ File.expand_path('client/echo_channel.rb', __dir__) ] + + Thread.new { EventMachine.run } unless EventMachine.reactor_running? + Thread.pass until EventMachine.reactor_running? + + # faye-websocket is warning-rich + @previous_verbose, $VERBOSE = $VERBOSE, nil + end + + def teardown + $VERBOSE = @previous_verbose + end + + def with_puma_server(rack_app = ActionCable.server, port = 3099) + server = ::Puma::Server.new(rack_app, ::Puma::Events.strings) + server.add_tcp_listener '127.0.0.1', port + server.min_threads = 1 + server.max_threads = 4 + + t = Thread.new { server.run.join } + yield port + + ensure + server.stop(true) if server + t.join if t + end + + class SyncClient + attr_reader :pings + + def initialize(port) + @ws = Faye::WebSocket::Client.new("ws://127.0.0.1:#{port}/") + @messages = Queue.new + @closed = Concurrent::Event.new + @has_messages = Concurrent::Semaphore.new(0) + @pings = 0 + + open = Concurrent::Event.new + error = nil + + @ws.on(:error) do |event| + if open.set? + @messages << RuntimeError.new(event.message) + else + error = event.message + open.set + end + end + + @ws.on(:open) do |event| + open.set + end + + @ws.on(:message) do |event| + hash = JSON.parse(event.data) + if hash['type'] == 'ping' + @pings += 1 + else + @messages << hash + @has_messages.release + end + end + + @ws.on(:close) do |event| + @closed.set + end + + open.wait(WAIT_WHEN_EXPECTING_EVENT) + raise error if error + end + + def read_message + @has_messages.try_acquire(1, WAIT_WHEN_EXPECTING_EVENT) + + msg = @messages.pop(true) + raise msg if msg.is_a?(Exception) + + msg + end + + def read_messages(expected_size = 0) + list = [] + loop do + if @has_messages.try_acquire(1, list.size < expected_size ? WAIT_WHEN_EXPECTING_EVENT : WAIT_WHEN_NOT_EXPECTING_EVENT) + msg = @messages.pop(true) + raise msg if msg.is_a?(Exception) + + list << msg + else + break + end + end + list + end + + def send_message(hash) + @ws.send(JSON.dump(hash)) + end + + def close + sleep WAIT_WHEN_NOT_EXPECTING_EVENT + + unless @messages.empty? + raise "#{@messages.size} messages unprocessed" + end + + @ws.close + wait_for_close + end + + def wait_for_close + @closed.wait(WAIT_WHEN_EXPECTING_EVENT) + end + + def closed? + @closed.set? + end + end + + def faye_client(port) + SyncClient.new(port) + end + + def test_single_client + with_puma_server do |port| + c = faye_client(port) + assert_equal({"type" => "welcome"}, c.read_message) # pop the first welcome message off the stack + c.send_message command: 'subscribe', identifier: JSON.dump(channel: 'EchoChannel') + assert_equal({"identifier"=>"{\"channel\":\"EchoChannel\"}", "type"=>"confirm_subscription"}, c.read_message) + c.send_message command: 'message', identifier: JSON.dump(channel: 'EchoChannel'), data: JSON.dump(action: 'ding', message: 'hello') + assert_equal({"identifier"=>"{\"channel\":\"EchoChannel\"}", "message"=>{"dong"=>"hello"}}, c.read_message) + c.close + end + end + + def test_interacting_clients + with_puma_server do |port| + clients = 10.times.map { faye_client(port) } + + barrier_1 = Concurrent::CyclicBarrier.new(clients.size) + barrier_2 = Concurrent::CyclicBarrier.new(clients.size) + + clients.map {|c| Concurrent::Future.execute { + assert_equal({"type" => "welcome"}, c.read_message) # pop the first welcome message off the stack + c.send_message command: 'subscribe', identifier: JSON.dump(channel: 'EchoChannel') + assert_equal({"identifier"=>'{"channel":"EchoChannel"}', "type"=>"confirm_subscription"}, c.read_message) + c.send_message command: 'message', identifier: JSON.dump(channel: 'EchoChannel'), data: JSON.dump(action: 'ding', message: 'hello') + assert_equal({"identifier"=>'{"channel":"EchoChannel"}', "message"=>{"dong"=>"hello"}}, c.read_message) + barrier_1.wait WAIT_WHEN_EXPECTING_EVENT + c.send_message command: 'message', identifier: JSON.dump(channel: 'EchoChannel'), data: JSON.dump(action: 'bulk', message: 'hello') + barrier_2.wait WAIT_WHEN_EXPECTING_EVENT + assert_equal clients.size, c.read_messages(clients.size).size + } }.each(&:wait!) + + clients.map {|c| Concurrent::Future.execute { c.close } }.each(&:wait!) + end + end + + def test_many_clients + with_puma_server do |port| + clients = 100.times.map { faye_client(port) } + + clients.map {|c| Concurrent::Future.execute { + assert_equal({"type" => "welcome"}, c.read_message) # pop the first welcome message off the stack + c.send_message command: 'subscribe', identifier: JSON.dump(channel: 'EchoChannel') + assert_equal({"identifier"=>'{"channel":"EchoChannel"}', "type"=>"confirm_subscription"}, c.read_message) + c.send_message command: 'message', identifier: JSON.dump(channel: 'EchoChannel'), data: JSON.dump(action: 'ding', message: 'hello') + assert_equal({"identifier"=>'{"channel":"EchoChannel"}', "message"=>{"dong"=>"hello"}}, c.read_message) + } }.each(&:wait!) + + clients.map {|c| Concurrent::Future.execute { c.close } }.each(&:wait!) + end + end + + def test_disappearing_client + with_puma_server do |port| + c = faye_client(port) + assert_equal({"type" => "welcome"}, c.read_message) # pop the first welcome message off the stack + c.send_message command: 'subscribe', identifier: JSON.dump(channel: 'EchoChannel') + assert_equal({"identifier"=>"{\"channel\":\"EchoChannel\"}", "type"=>"confirm_subscription"}, c.read_message) + c.send_message command: 'message', identifier: JSON.dump(channel: 'EchoChannel'), data: JSON.dump(action: 'delay', message: 'hello') + c.close # disappear before write + + c = faye_client(port) + assert_equal({"type" => "welcome"}, c.read_message) # pop the first welcome message off the stack + c.send_message command: 'subscribe', identifier: JSON.dump(channel: 'EchoChannel') + assert_equal({"identifier"=>"{\"channel\":\"EchoChannel\"}", "type"=>"confirm_subscription"}, c.read_message) + c.send_message command: 'message', identifier: JSON.dump(channel: 'EchoChannel'), data: JSON.dump(action: 'ding', message: 'hello') + assert_equal({"identifier"=>'{"channel":"EchoChannel"}', "message"=>{"dong"=>"hello"}}, c.read_message) + c.close # disappear before read + end + end + + def test_unsubscribe_client + with_puma_server do |port| + app = ActionCable.server + identifier = JSON.dump(channel: 'EchoChannel') + + c = faye_client(port) + assert_equal({"type" => "welcome"}, c.read_message) + c.send_message command: 'subscribe', identifier: identifier + assert_equal({"identifier"=>"{\"channel\":\"EchoChannel\"}", "type"=>"confirm_subscription"}, c.read_message) + assert_equal(1, app.connections.count) + assert(app.remote_connections.where(identifier: identifier)) + + channel = app.connections.first.subscriptions.send(:subscriptions).first[1] + channel.expects(:unsubscribed) + c.close + sleep 0.1 # Data takes a moment to process + + # All data is removed: No more connection or subscription information! + assert_equal(0, app.connections.count) + end + end + + def test_server_restart + with_puma_server do |port| + c = faye_client(port) + assert_equal({"type" => "welcome"}, c.read_message) + c.send_message command: 'subscribe', identifier: JSON.dump(channel: 'EchoChannel') + assert_equal({"identifier"=>"{\"channel\":\"EchoChannel\"}", "type"=>"confirm_subscription"}, c.read_message) + + ActionCable.server.restart + c.wait_for_close + assert c.closed? + end + end +end diff --git a/actioncable/test/connection/authorization_test.rb b/actioncable/test/connection/authorization_test.rb index 87d0e79ef3..a0506cb9c0 100644 --- a/actioncable/test/connection/authorization_test.rb +++ b/actioncable/test/connection/authorization_test.rb @@ -20,7 +20,7 @@ class ActionCable::Connection::AuthorizationTest < ActionCable::TestCase server.config.allowed_request_origins = %w( http://rubyonrails.com ) env = Rack::MockRequest.env_for "/test", 'HTTP_CONNECTION' => 'upgrade', 'HTTP_UPGRADE' => 'websocket', - 'HTTP_ORIGIN' => 'http://rubyonrails.com' + 'HTTP_HOST' => 'localhost', 'HTTP_ORIGIN' => 'http://rubyonrails.com' connection = Connection.new(server, env) connection.websocket.expects(:close) diff --git a/actioncable/test/connection/base_test.rb b/actioncable/test/connection/base_test.rb index 182562db82..d7e1041e68 100644 --- a/actioncable/test/connection/base_test.rb +++ b/actioncable/test/connection/base_test.rb @@ -1,5 +1,6 @@ require 'test_helper' require 'stubs/test_server' +require 'active_support/core_ext/object/json' class ActionCable::Connection::BaseTest < ActionCable::TestCase class Connection < ActionCable::Connection::Base @@ -37,6 +38,8 @@ class ActionCable::Connection::BaseTest < ActionCable::TestCase connection.process assert connection.websocket.possible? + + wait_for_async assert connection.websocket.alive? end end @@ -53,16 +56,15 @@ class ActionCable::Connection::BaseTest < ActionCable::TestCase test "on connection open" do run_in_eventmachine do connection = open_connection - connection.process - connection.websocket.expects(:transmit).with(regexp_matches(/\_ping/)) + connection.websocket.expects(:transmit).with({ type: "welcome" }.to_json) connection.message_buffer.expects(:process!) - # Allow EM to run on_open callback - EM.next_tick do - assert_equal [ connection ], @server.connections - assert connection.connected - end + connection.process + wait_for_async + + assert_equal [ connection ], @server.connections + assert connection.connected end end @@ -72,12 +74,12 @@ class ActionCable::Connection::BaseTest < ActionCable::TestCase connection.process # Setup the connection - EventMachine.stubs(:add_periodic_timer).returns(true) - connection.send :on_open + connection.server.stubs(:timer).returns(true) + connection.send :handle_open assert connection.connected connection.subscriptions.expects(:unsubscribe_from_all) - connection.send :on_close + connection.send :handle_close assert ! connection.connected assert_equal [], @server.connections @@ -107,10 +109,30 @@ class ActionCable::Connection::BaseTest < ActionCable::TestCase end end + test "rejecting a connection causes a 404" do + run_in_eventmachine do + class CallMeMaybe + def call(*) + raise 'Do not call me!' + end + end + + env = Rack::MockRequest.env_for( + "/test", + { 'HTTP_CONNECTION' => 'upgrade', 'HTTP_UPGRADE' => 'websocket', + 'HTTP_HOST' => 'localhost', 'HTTP_ORIGIN' => 'http://rubyonrails.org', 'rack.hijack' => CallMeMaybe.new } + ) + + connection = ActionCable::Connection::Base.new(@server, env) + response = connection.process + assert_equal 404, response[0] + end + end + private def open_connection env = Rack::MockRequest.env_for "/test", 'HTTP_CONNECTION' => 'upgrade', 'HTTP_UPGRADE' => 'websocket', - 'HTTP_ORIGIN' => 'http://rubyonrails.com' + 'HTTP_HOST' => 'localhost', 'HTTP_ORIGIN' => 'http://rubyonrails.com' Connection.new(@server, env) end diff --git a/actioncable/test/connection/cross_site_forgery_test.rb b/actioncable/test/connection/cross_site_forgery_test.rb index a29f65fb97..2d516b0533 100644 --- a/actioncable/test/connection/cross_site_forgery_test.rb +++ b/actioncable/test/connection/cross_site_forgery_test.rb @@ -76,6 +76,6 @@ class ActionCable::Connection::CrossSiteForgeryTest < ActionCable::TestCase def env_for_origin(origin) Rack::MockRequest.env_for "/test", 'HTTP_CONNECTION' => 'upgrade', 'HTTP_UPGRADE' => 'websocket', 'SERVER_NAME' => HOST, - 'HTTP_ORIGIN' => origin + 'HTTP_HOST' => HOST, 'HTTP_ORIGIN' => origin end end diff --git a/actioncable/test/connection/identifier_test.rb b/actioncable/test/connection/identifier_test.rb index a110dfdee0..c3d5f1f90b 100644 --- a/actioncable/test/connection/identifier_test.rb +++ b/actioncable/test/connection/identifier_test.rb @@ -64,14 +64,14 @@ class ActionCable::Connection::IdentifierTest < ActionCable::TestCase end def open_connection(server:) - env = Rack::MockRequest.env_for "/test", 'HTTP_CONNECTION' => 'upgrade', 'HTTP_UPGRADE' => 'websocket' + env = Rack::MockRequest.env_for "/test", 'HTTP_HOST' => 'localhost', 'HTTP_CONNECTION' => 'upgrade', 'HTTP_UPGRADE' => 'websocket' @connection = Connection.new(server, env) @connection.process - @connection.send :on_open + @connection.send :handle_open end def close_connection - @connection.send :on_close + @connection.send :handle_close end end diff --git a/actioncable/test/connection/multiple_identifiers_test.rb b/actioncable/test/connection/multiple_identifiers_test.rb index 55a9f96cb3..484e73bb30 100644 --- a/actioncable/test/connection/multiple_identifiers_test.rb +++ b/actioncable/test/connection/multiple_identifiers_test.rb @@ -28,14 +28,14 @@ class ActionCable::Connection::MultipleIdentifiersTest < ActionCable::TestCase end def open_connection(server:) - env = Rack::MockRequest.env_for "/test", 'HTTP_CONNECTION' => 'upgrade', 'HTTP_UPGRADE' => 'websocket' + env = Rack::MockRequest.env_for "/test", 'HTTP_HOST' => 'localhost', 'HTTP_CONNECTION' => 'upgrade', 'HTTP_UPGRADE' => 'websocket' @connection = Connection.new(server, env) @connection.process - @connection.send :on_open + @connection.send :handle_open end def close_connection - @connection.send :on_close + @connection.send :handle_close end end diff --git a/actioncable/test/connection/string_identifier_test.rb b/actioncable/test/connection/string_identifier_test.rb index 9d0bda83ef..eca0c31060 100644 --- a/actioncable/test/connection/string_identifier_test.rb +++ b/actioncable/test/connection/string_identifier_test.rb @@ -30,7 +30,7 @@ class ActionCable::Connection::StringIdentifierTest < ActionCable::TestCase end def open_connection - env = Rack::MockRequest.env_for "/test", 'HTTP_CONNECTION' => 'upgrade', 'HTTP_UPGRADE' => 'websocket' + env = Rack::MockRequest.env_for "/test", 'HTTP_HOST' => 'localhost', 'HTTP_CONNECTION' => 'upgrade', 'HTTP_UPGRADE' => 'websocket' @connection = Connection.new(@server, env) @connection.process diff --git a/actioncable/test/connection/subscriptions_test.rb b/actioncable/test/connection/subscriptions_test.rb index 62e41484fe..f91597f567 100644 --- a/actioncable/test/connection/subscriptions_test.rb +++ b/actioncable/test/connection/subscriptions_test.rb @@ -82,13 +82,13 @@ class ActionCable::Connection::SubscriptionsTest < ActionCable::TestCase end end - test "unsubscrib from all" do + test "unsubscribe from all" do run_in_eventmachine do setup_connection channel1 = subscribe_to_chat_channel - channel2_id = ActiveSupport::JSON.encode(id: 2, channel: 'ActionCable::Connection::SubscriptionsTest::ChatChannel') + channel2_id = ActiveSupport::JSON.encode({ id: 2, channel: 'ActionCable::Connection::SubscriptionsTest::ChatChannel' }) channel2 = subscribe_to_chat_channel(channel2_id) channel1.expects(:unsubscribe_from_channel) @@ -107,7 +107,7 @@ class ActionCable::Connection::SubscriptionsTest < ActionCable::TestCase end def setup_connection - env = Rack::MockRequest.env_for "/test", 'HTTP_CONNECTION' => 'upgrade', 'HTTP_UPGRADE' => 'websocket' + env = Rack::MockRequest.env_for "/test", 'HTTP_HOST' => 'localhost', 'HTTP_CONNECTION' => 'upgrade', 'HTTP_UPGRADE' => 'websocket' @connection = Connection.new(@server, env) @subscriptions = ActionCable::Connection::Subscriptions.new(@connection) diff --git a/actioncable/test/server/broadcasting_test.rb b/actioncable/test/server/broadcasting_test.rb new file mode 100644 index 0000000000..3b4a7eaf90 --- /dev/null +++ b/actioncable/test/server/broadcasting_test.rb @@ -0,0 +1,15 @@ +require "test_helper" + +class BroadcastingTest < ActiveSupport::TestCase + class TestServer + include ActionCable::Server::Broadcasting + end + + test "fetching a broadcaster converts the broadcasting queue to a string" do + broadcasting = :test_queue + server = TestServer.new + broadcaster = server.broadcaster_for(broadcasting) + + assert_equal "test_queue", broadcaster.broadcasting + end +end diff --git a/actioncable/test/stubs/test_connection.rb b/actioncable/test/stubs/test_connection.rb index da98201900..8ba284fdc6 100644 --- a/actioncable/test/stubs/test_connection.rb +++ b/actioncable/test/stubs/test_connection.rb @@ -1,18 +1,19 @@ require 'stubs/user' class TestConnection - attr_reader :identifiers, :logger, :current_user, :transmissions + attr_reader :identifiers, :logger, :current_user, :server, :transmissions def initialize(user = User.new("lifo")) @identifiers = [ :current_user ] @current_user = user @logger = ActiveSupport::TaggedLogging.new ActiveSupport::Logger.new(StringIO.new) + @server = TestServer.new @transmissions = [] end def pubsub - SuccessAdapter.new(TestServer.new) + SuccessAdapter.new(server) end def transmit(data) diff --git a/actioncable/test/stubs/test_server.rb b/actioncable/test/stubs/test_server.rb index 6e6541a952..9e860825f3 100644 --- a/actioncable/test/stubs/test_server.rb +++ b/actioncable/test/stubs/test_server.rb @@ -8,12 +8,27 @@ class TestServer def initialize @logger = ActiveSupport::TaggedLogging.new ActiveSupport::Logger.new(StringIO.new) @config = OpenStruct.new(log_tags: [], subscription_adapter: SuccessAdapter) + @config.use_faye = ENV['FAYE'].present? + @config.client_socket_class = if @config.use_faye + ActionCable::Connection::FayeClientSocket + else + ActionCable::Connection::ClientSocket + end end def pubsub @config.subscription_adapter.new(self) end - def send_async + def event_loop + @event_loop ||= if @config.use_faye + ActionCable::Connection::FayeEventLoop.new + else + ActionCable::Connection::StreamEventLoop.new + end + end + + def worker_pool + @worker_pool ||= ActionCable::Server::Worker.new(max_size: 5) end end diff --git a/actioncable/test/subscription_adapter/base_test.rb b/actioncable/test/subscription_adapter/base_test.rb index 7a7ae131e6..256dce673f 100644 --- a/actioncable/test/subscription_adapter/base_test.rb +++ b/actioncable/test/subscription_adapter/base_test.rb @@ -43,7 +43,7 @@ class ActionCable::SubscriptionAdapter::BaseTest < ActionCable::TestCase assert_respond_to(SuccessAdapter.new(@server), :broadcast) - assert_nothing_raised NotImplementedError do + assert_nothing_raised do broadcast end end @@ -55,7 +55,7 @@ class ActionCable::SubscriptionAdapter::BaseTest < ActionCable::TestCase assert_respond_to(SuccessAdapter.new(@server), :subscribe) - assert_nothing_raised NotImplementedError do + assert_nothing_raised do subscribe end end @@ -66,7 +66,7 @@ class ActionCable::SubscriptionAdapter::BaseTest < ActionCable::TestCase assert_respond_to(SuccessAdapter.new(@server), :unsubscribe) - assert_nothing_raised NotImplementedError do + assert_nothing_raised do unsubscribe end end diff --git a/actioncable/test/subscription_adapter/common.rb b/actioncable/test/subscription_adapter/common.rb index d4a13be889..82f0abbbf3 100644 --- a/actioncable/test/subscription_adapter/common.rb +++ b/actioncable/test/subscription_adapter/common.rb @@ -1,7 +1,6 @@ require 'test_helper' require 'concurrent' -require 'action_cable/process/logging' require 'active_support/core_ext/hash/indifferent_access' require 'pathname' @@ -10,23 +9,9 @@ module CommonSubscriptionAdapterTest WAIT_WHEN_NOT_EXPECTING_EVENT = 0.2 def setup - # TODO: ActionCable requires a *lot* of setup at the moment... - ::Object.const_set(:ApplicationCable, Module.new) - ::ApplicationCable.const_set(:Connection, Class.new(ActionCable::Connection::Base)) - - ::Object.const_set(:Rails, Module.new) - ::Rails.singleton_class.send(:define_method, :root) { Pathname.new(__dir__) } - server = ActionCable::Server::Base.new - server.config = ActionCable::Server::Configuration.new - inner_logger = Logger.new(StringIO.new).tap { |l| l.level = Logger::UNKNOWN } - server.config.logger = ActionCable::Connection::TaggedLoggerProxy.new(inner_logger, tags: []) - - - # and now the "real" setup for our test: - spawn_eventmachine - server.config.cable = cable_config.with_indifferent_access + server.config.use_faye = ENV['FAYE'].present? adapter_klass = server.config.pubsub_adapter @@ -37,15 +22,6 @@ module CommonSubscriptionAdapterTest def teardown @tx_adapter.shutdown if @tx_adapter && @tx_adapter != @rx_adapter @rx_adapter.shutdown if @rx_adapter - - begin - ::Object.send(:remove_const, :ApplicationCable) - rescue NameError - end - begin - ::Object.send(:remove_const, :Rails) - rescue NameError - end end diff --git a/actioncable/test/subscription_adapter/evented_redis_test.rb b/actioncable/test/subscription_adapter/evented_redis_test.rb new file mode 100644 index 0000000000..70333e51bd --- /dev/null +++ b/actioncable/test/subscription_adapter/evented_redis_test.rb @@ -0,0 +1,10 @@ +require 'test_helper' +require_relative './common' + +class EventedRedisAdapterTest < ActionCable::TestCase + include CommonSubscriptionAdapterTest + + def cable_config + { adapter: 'evented_redis', url: 'redis://127.0.0.1:6379/12' } + end +end diff --git a/actioncable/test/subscription_adapter/postgresql_test.rb b/actioncable/test/subscription_adapter/postgresql_test.rb index 64c632b0cd..3b4fadbb2a 100644 --- a/actioncable/test/subscription_adapter/postgresql_test.rb +++ b/actioncable/test/subscription_adapter/postgresql_test.rb @@ -15,8 +15,15 @@ class PostgresqlAdapterTest < ActionCable::TestCase local_config = ARTest.config['arunit'] database_config.update local_config if local_config end + ActiveRecord::Base.establish_connection database_config + begin + ActiveRecord::Base.connection + rescue + skip "Couldn't connect to PostgreSQL: #{database_config.inspect}" + end + super end diff --git a/actioncable/test/subscription_adapter/redis_test.rb b/actioncable/test/subscription_adapter/redis_test.rb index 8d52832c87..4f34dd86c9 100644 --- a/actioncable/test/subscription_adapter/redis_test.rb +++ b/actioncable/test/subscription_adapter/redis_test.rb @@ -5,6 +5,12 @@ class RedisAdapterTest < ActionCable::TestCase include CommonSubscriptionAdapterTest def cable_config - { adapter: 'redis', url: 'redis://127.0.0.1:6379/12' } + { adapter: 'redis', driver: 'ruby', url: 'redis://127.0.0.1:6379/12' } + end +end + +class RedisAdapterTest::Hiredis < RedisAdapterTest + def cable_config + super.merge(driver: 'hiredis') end end diff --git a/actioncable/test/test_helper.rb b/actioncable/test/test_helper.rb index 6636ce078b..030362d512 100644 --- a/actioncable/test/test_helper.rb +++ b/actioncable/test/test_helper.rb @@ -1,40 +1,68 @@ -require File.expand_path('../../../load_paths', __FILE__) - require 'action_cable' require 'active_support/testing/autorun' - require 'puma' require 'mocha/setup' require 'rack/mock' +require 'active_support/core_ext/hash/indifferent_access' # Require all the stubs and models Dir[File.dirname(__FILE__) + '/stubs/*.rb'].each {|file| require file } -require 'faye/websocket' -class << Faye::WebSocket - remove_method :ensure_reactor_running +if ENV['FAYE'].present? + require 'faye/websocket' + class << Faye::WebSocket + remove_method :ensure_reactor_running - # We don't want Faye to start the EM reactor in tests because it makes testing much harder. - # We want to be able to start and stop EM loop in tests to make things simpler. - def ensure_reactor_running - # no-op + # We don't want Faye to start the EM reactor in tests because it makes testing much harder. + # We want to be able to start and stop EM loop in tests to make things simpler. + def ensure_reactor_running + # no-op + end end end -class ActionCable::TestCase < ActiveSupport::TestCase +module EventMachineConcurrencyHelpers + def wait_for_async + EM.run_deferred_callbacks + end + def run_in_eventmachine + failure = nil EM.run do - yield + begin + yield + rescue => ex + failure = ex + ensure + wait_for_async + EM.stop if EM.reactor_running? + end + end + raise failure if failure + end +end - EM.run_deferred_callbacks - EM.stop +module ConcurrentRubyConcurrencyHelpers + def wait_for_async + e = Concurrent.global_io_executor + until e.completed_task_count == e.scheduled_task_count + sleep 0.1 end end - def spawn_eventmachine - Thread.new { EventMachine.run } unless EventMachine.reactor_running? + def run_in_eventmachine + yield + wait_for_async + end +end + +class ActionCable::TestCase < ActiveSupport::TestCase + if ENV['FAYE'].present? + include EventMachineConcurrencyHelpers + else + include ConcurrentRubyConcurrencyHelpers end end diff --git a/actioncable/test/worker_test.rb b/actioncable/test/worker_test.rb index 4a699cde27..654f49821e 100644 --- a/actioncable/test/worker_test.rb +++ b/actioncable/test/worker_test.rb @@ -17,7 +17,9 @@ class WorkerTest < ActiveSupport::TestCase end def logger - ActionCable.server.logger + # Impersonating a connection requires a TaggedLoggerProxy'ied logger. + inner_logger = Logger.new(StringIO.new).tap { |l| l.level = Logger::UNKNOWN } + ActionCable::Connection::TaggedLoggerProxy.new(inner_logger, tags: []) end end |