From 9ff28c10ebad0d4781603499a3e3b1d7fd5fbd2c Mon Sep 17 00:00:00 2001 From: Matthew Draper Date: Sun, 24 Jan 2016 21:13:40 +1030 Subject: Add tests for the ActionCable adapters --- Gemfile | 3 + Gemfile.lock | 6 + .../test/subscription_adapter/async_test.rb | 17 +++ actioncable/test/subscription_adapter/common.rb | 142 +++++++++++++++++++++ .../test/subscription_adapter/inline_test.rb | 17 +++ .../test/subscription_adapter/postgresql_test.rb | 32 +++++ .../test/subscription_adapter/redis_test.rb | 10 ++ actioncable/test/test_helper.rb | 4 + actioncable/test/worker_test.rb | 5 + 9 files changed, 236 insertions(+) create mode 100644 actioncable/test/subscription_adapter/async_test.rb create mode 100644 actioncable/test/subscription_adapter/common.rb create mode 100644 actioncable/test/subscription_adapter/inline_test.rb create mode 100644 actioncable/test/subscription_adapter/postgresql_test.rb create mode 100644 actioncable/test/subscription_adapter/redis_test.rb diff --git a/Gemfile b/Gemfile index 1fbf1dd52e..b69c05025f 100644 --- a/Gemfile +++ b/Gemfile @@ -63,6 +63,9 @@ end # Action Cable group :cable do gem 'puma', require: false + + gem 'em-hiredis', require: false + gem 'redis', require: false end # Add your own local bundler stuff. diff --git a/Gemfile.lock b/Gemfile.lock index 6b29d2c44b..0789c59905 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -139,6 +139,9 @@ GEM delayed_job_active_record (4.1.0) activerecord (>= 3.0, < 5) delayed_job (>= 3.0, < 5) + em-hiredis (0.3.0) + eventmachine (~> 1.0) + hiredis (~> 0.5.0) erubis (2.7.0) eventmachine (1.0.9.1) execjs (2.6.0) @@ -150,6 +153,7 @@ GEM ffi (1.9.10-x86-mingw32) globalid (0.3.6) activesupport (>= 4.1.0) + hiredis (0.5.2) hitimes (1.2.3) hitimes (1.2.3-x86-mingw32) i18n (0.7.0) @@ -302,6 +306,7 @@ DEPENDENCIES dalli (>= 2.2.1) delayed_job delayed_job_active_record + em-hiredis jquery-rails json kindlerb (= 0.1.1) @@ -322,6 +327,7 @@ DEPENDENCIES rails! rake (>= 10.3) redcarpet (~> 3.2.3) + redis resque resque-scheduler sass! diff --git a/actioncable/test/subscription_adapter/async_test.rb b/actioncable/test/subscription_adapter/async_test.rb new file mode 100644 index 0000000000..8f413f14c2 --- /dev/null +++ b/actioncable/test/subscription_adapter/async_test.rb @@ -0,0 +1,17 @@ +require 'test_helper' +require_relative './common' + +class AsyncAdapterTest < ActionCable::TestCase + include CommonSubscriptionAdapterTest + + def setup + super + + @tx_adapter.shutdown + @tx_adapter = @rx_adapter + end + + def cable_config + { adapter: 'async' } + end +end diff --git a/actioncable/test/subscription_adapter/common.rb b/actioncable/test/subscription_adapter/common.rb new file mode 100644 index 0000000000..d4a13be889 --- /dev/null +++ b/actioncable/test/subscription_adapter/common.rb @@ -0,0 +1,142 @@ +require 'test_helper' +require 'concurrent' + +require 'action_cable/process/logging' +require 'active_support/core_ext/hash/indifferent_access' +require 'pathname' + +module CommonSubscriptionAdapterTest + WAIT_WHEN_EXPECTING_EVENT = 3 + 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 + + adapter_klass = server.config.pubsub_adapter + + @rx_adapter = adapter_klass.new(server) + @tx_adapter = adapter_klass.new(server) + end + + 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 + + + def subscribe_as_queue(channel, adapter = @rx_adapter) + queue = Queue.new + + callback = -> data { queue << data } + subscribed = Concurrent::Event.new + adapter.subscribe(channel, callback, Proc.new { subscribed.set }) + subscribed.wait(WAIT_WHEN_EXPECTING_EVENT) + assert subscribed.set? + + yield queue + + sleep WAIT_WHEN_NOT_EXPECTING_EVENT + assert_empty queue + ensure + adapter.unsubscribe(channel, callback) if subscribed.set? + end + + + def test_subscribe_and_unsubscribe + subscribe_as_queue('channel') do |queue| + end + end + + def test_basic_broadcast + subscribe_as_queue('channel') do |queue| + @tx_adapter.broadcast('channel', 'hello world') + + assert_equal 'hello world', queue.pop + end + end + + def test_broadcast_after_unsubscribe + keep_queue = nil + subscribe_as_queue('channel') do |queue| + keep_queue = queue + + @tx_adapter.broadcast('channel', 'hello world') + + assert_equal 'hello world', queue.pop + end + + @tx_adapter.broadcast('channel', 'hello void') + + sleep WAIT_WHEN_NOT_EXPECTING_EVENT + assert_empty keep_queue + end + + def test_multiple_broadcast + subscribe_as_queue('channel') do |queue| + @tx_adapter.broadcast('channel', 'bananas') + @tx_adapter.broadcast('channel', 'apples') + + received = [] + 2.times { received << queue.pop } + assert_equal ['apples', 'bananas'], received.sort + end + end + + def test_identical_subscriptions + subscribe_as_queue('channel') do |queue| + subscribe_as_queue('channel') do |queue_2| + @tx_adapter.broadcast('channel', 'hello') + + assert_equal 'hello', queue_2.pop + end + + assert_equal 'hello', queue.pop + end + end + + def test_simultaneous_subscriptions + subscribe_as_queue('channel') do |queue| + subscribe_as_queue('other channel') do |queue_2| + @tx_adapter.broadcast('channel', 'apples') + @tx_adapter.broadcast('other channel', 'oranges') + + assert_equal 'apples', queue.pop + assert_equal 'oranges', queue_2.pop + end + end + end + + def test_channel_filtered_broadcast + subscribe_as_queue('channel') do |queue| + @tx_adapter.broadcast('other channel', 'one') + @tx_adapter.broadcast('channel', 'two') + + assert_equal 'two', queue.pop + end + end +end diff --git a/actioncable/test/subscription_adapter/inline_test.rb b/actioncable/test/subscription_adapter/inline_test.rb new file mode 100644 index 0000000000..75ea51e6b3 --- /dev/null +++ b/actioncable/test/subscription_adapter/inline_test.rb @@ -0,0 +1,17 @@ +require 'test_helper' +require_relative './common' + +class InlineAdapterTest < ActionCable::TestCase + include CommonSubscriptionAdapterTest + + def setup + super + + @tx_adapter.shutdown + @tx_adapter = @rx_adapter + end + + def cable_config + { adapter: 'inline' } + end +end diff --git a/actioncable/test/subscription_adapter/postgresql_test.rb b/actioncable/test/subscription_adapter/postgresql_test.rb new file mode 100644 index 0000000000..64c632b0cd --- /dev/null +++ b/actioncable/test/subscription_adapter/postgresql_test.rb @@ -0,0 +1,32 @@ +require 'test_helper' +require_relative './common' + +require 'active_record' + +class PostgresqlAdapterTest < ActionCable::TestCase + include CommonSubscriptionAdapterTest + + def setup + database_config = { 'adapter' => 'postgresql', 'database' => 'activerecord_unittest' } + ar_tests = File.expand_path('../../../activerecord/test', __dir__) + if Dir.exist?(ar_tests) + require File.join(ar_tests, 'config') + require File.join(ar_tests, 'support/config') + local_config = ARTest.config['arunit'] + database_config.update local_config if local_config + end + ActiveRecord::Base.establish_connection database_config + + super + end + + def teardown + super + + ActiveRecord::Base.clear_all_connections! + end + + def cable_config + { adapter: 'postgresql' } + end +end diff --git a/actioncable/test/subscription_adapter/redis_test.rb b/actioncable/test/subscription_adapter/redis_test.rb new file mode 100644 index 0000000000..8d52832c87 --- /dev/null +++ b/actioncable/test/subscription_adapter/redis_test.rb @@ -0,0 +1,10 @@ +require 'test_helper' +require_relative './common' + +class RedisAdapterTest < ActionCable::TestCase + include CommonSubscriptionAdapterTest + + def cable_config + { adapter: 'redis', url: 'redis://127.0.0.1:6379/12' } + end +end diff --git a/actioncable/test/test_helper.rb b/actioncable/test/test_helper.rb index 65b45e0c89..6636ce078b 100644 --- a/actioncable/test/test_helper.rb +++ b/actioncable/test/test_helper.rb @@ -33,4 +33,8 @@ class ActionCable::TestCase < ActiveSupport::TestCase EM.stop end end + + def spawn_eventmachine + Thread.new { EventMachine.run } unless EventMachine.reactor_running? + end end diff --git a/actioncable/test/worker_test.rb b/actioncable/test/worker_test.rb index 9911a3b98b..4a699cde27 100644 --- a/actioncable/test/worker_test.rb +++ b/actioncable/test/worker_test.rb @@ -13,6 +13,11 @@ class WorkerTest < ActiveSupport::TestCase end def connection + self + end + + def logger + ActionCable.server.logger end end -- cgit v1.2.3