diff options
Diffstat (limited to 'actioncable')
44 files changed, 181 insertions, 281 deletions
diff --git a/actioncable/.gitignore b/actioncable/.gitignore index 0a04b29786..f514e58c16 100644 --- a/actioncable/.gitignore +++ b/actioncable/.gitignore @@ -1,2 +1,2 @@ -/lib/assets/compiled -/tmp +/lib/assets/compiled/ +/tmp/ diff --git a/actioncable/CHANGELOG.md b/actioncable/CHANGELOG.md index 560ee89846..959943016f 100644 --- a/actioncable/CHANGELOG.md +++ b/actioncable/CHANGELOG.md @@ -1,26 +1,6 @@ -* Hash long stream identifiers when using PostgreSQL adapter. +* Rails 6 requires Ruby 2.4.1 or newer. - PostgreSQL has a limit on identifiers length (63 chars, [docs](https://www.postgresql.org/docs/current/static/sql-syntax-lexical.html#SQL-SYNTAX-IDENTIFIERS)). - Provided fix minifies identifiers longer than 63 chars by hashing them with SHA1. + *Jeremy Daer* - Fixes #28751. - *Vladimir Dementyev* - -* Action Cable's `redis` adapter allows for other common redis-rb options (`host`, `port`, `db`, `password`) in cable.yml. - - Previously, it accepts only a [redis:// url](https://www.iana.org/assignments/uri-schemes/prov/redis) as an option. - While we can add all of these options to the `url` itself, it is not explicitly documented. This alternative setup - is shown as the first example in the [Redis rubygem](https://github.com/redis/redis-rb#getting-started), which - makes this set of options as sensible as using just the `url`. - - *Marc Rendl Ignacio* - -* Action Cable socket errors are now logged to the console - - Previously any socket errors were ignored and this made it hard to diagnose socket issues (e.g. as discussed in #28362). - - *Edward Poot* - - -Please check [5-1-stable](https://github.com/rails/rails/blob/5-1-stable/actioncable/CHANGELOG.md) for previous changes. +Please check [5-2-stable](https://github.com/rails/rails/blob/5-2-stable/actioncable/CHANGELOG.md) for previous changes. diff --git a/actioncable/MIT-LICENSE b/actioncable/MIT-LICENSE index 1a0e653b69..a42759f024 100644 --- a/actioncable/MIT-LICENSE +++ b/actioncable/MIT-LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2015-2017 Basecamp, LLC +Copyright (c) 2015-2018 Basecamp, LLC Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the diff --git a/actioncable/README.md b/actioncable/README.md index 70b39ead57..a05ef1dd20 100644 --- a/actioncable/README.md +++ b/actioncable/README.md @@ -446,7 +446,7 @@ The WebSocket server doesn't have access to the session, but it has access to th ## Dependencies -Action Cable provides a subscription adapter interface to process its pubsub internals. By default, asynchronous, inline, PostgreSQL, evented Redis, and non-evented Redis adapters are included. The default adapter in new Rails applications is the asynchronous (`async`) adapter. To create your own adapter, you can look at `ActionCable::SubscriptionAdapter::Base` for all methods that must be implemented, and any of the adapters included within Action Cable as example implementations. +Action Cable provides a subscription adapter interface to process its pubsub internals. By default, asynchronous, inline, PostgreSQL, and Redis adapters are included. The default adapter in new Rails applications is the asynchronous (`async`) adapter. To create your own adapter, you can look at `ActionCable::SubscriptionAdapter::Base` for all methods that must be implemented, and any of the adapters included within Action Cable as example implementations. The Ruby side of things is built on top of [websocket-driver](https://github.com/faye/websocket-driver-ruby), [nio4r](https://github.com/celluloid/nio4r), and [concurrent-ruby](https://github.com/ruby-concurrency/concurrent-ruby). @@ -558,7 +558,7 @@ API documentation is at: * http://api.rubyonrails.org -Bug reports can be filed for the Ruby on Rails project here: +Bug reports for the Ruby on Rails project can be filed here: * https://github.com/rails/rails/issues diff --git a/actioncable/actioncable.gemspec b/actioncable/actioncable.gemspec index b5b98f1a6b..d946d0797f 100644 --- a/actioncable/actioncable.gemspec +++ b/actioncable/actioncable.gemspec @@ -9,7 +9,7 @@ Gem::Specification.new do |s| s.summary = "WebSocket framework for Rails." s.description = "Structure many real-time application concerns into channels over a single WebSocket connection." - s.required_ruby_version = ">= 2.2.2" + s.required_ruby_version = ">= 2.4.1" s.license = "MIT" @@ -28,5 +28,5 @@ Gem::Specification.new do |s| s.add_dependency "actionpack", version s.add_dependency "nio4r", "~> 2.0" - s.add_dependency "websocket-driver", "~> 0.6.1" + s.add_dependency "websocket-driver", ">= 0.6.1" end diff --git a/actioncable/lib/action_cable.rb b/actioncable/lib/action_cable.rb index bd828b2d0f..e7456e3c1b 100644 --- a/actioncable/lib/action_cable.rb +++ b/actioncable/lib/action_cable.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true #-- -# Copyright (c) 2015-2017 Basecamp, LLC +# Copyright (c) 2015-2018 Basecamp, LLC # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the @@ -25,7 +25,7 @@ require "active_support" require "active_support/rails" -require_relative "action_cable/version" +require "action_cable/version" module ActionCable extend ActiveSupport::Autoload diff --git a/actioncable/lib/action_cable/channel/broadcasting.rb b/actioncable/lib/action_cable/channel/broadcasting.rb index acc791817b..9a96720f4a 100644 --- a/actioncable/lib/action_cable/channel/broadcasting.rb +++ b/actioncable/lib/action_cable/channel/broadcasting.rb @@ -9,7 +9,7 @@ module ActionCable delegate :broadcasting_for, to: :class - class_methods do + module ClassMethods # Broadcast a hash to a unique broadcasting for this <tt>model</tt> in this channel. def broadcast_to(model, message) ActionCable.server.broadcast(broadcasting_for([ channel_name, model ]), message) diff --git a/actioncable/lib/action_cable/channel/callbacks.rb b/actioncable/lib/action_cable/channel/callbacks.rb index 4223c0d996..e4cb19b26a 100644 --- a/actioncable/lib/action_cable/channel/callbacks.rb +++ b/actioncable/lib/action_cable/channel/callbacks.rb @@ -13,7 +13,7 @@ module ActionCable define_callbacks :unsubscribe end - class_methods do + module ClassMethods def before_subscribe(*methods, &block) set_callback(:subscribe, :before, *methods, &block) end diff --git a/actioncable/lib/action_cable/channel/naming.rb b/actioncable/lib/action_cable/channel/naming.rb index 03a5dcd3a0..9c324a2a53 100644 --- a/actioncable/lib/action_cable/channel/naming.rb +++ b/actioncable/lib/action_cable/channel/naming.rb @@ -5,7 +5,7 @@ module ActionCable module Naming extend ActiveSupport::Concern - class_methods do + module ClassMethods # Returns the name of the channel, underscored, without the <tt>Channel</tt> ending. # If the channel is in a namespace, then the namespaces are represented by single # colon separators in the channel name. diff --git a/actioncable/lib/action_cable/connection/base.rb b/actioncable/lib/action_cable/connection/base.rb index 84053db9fd..11a1f1a5e8 100644 --- a/actioncable/lib/action_cable/connection/base.rb +++ b/actioncable/lib/action_cable/connection/base.rb @@ -136,13 +136,10 @@ module ActionCable send_async :handle_close end - # TODO Change this to private once we've dropped Ruby 2.2 support. - # Workaround for Ruby 2.2 "private attribute?" warning. - protected + private attr_reader :websocket attr_reader :message_buffer - private # The request that initiated the WebSocket connection is available here. This gives access to the environment, cookies, etc. def request # :doc: @request ||= begin diff --git a/actioncable/lib/action_cable/connection/client_socket.rb b/actioncable/lib/action_cable/connection/client_socket.rb index ba33c8b982..4b1964c4ae 100644 --- a/actioncable/lib/action_cable/connection/client_socket.rb +++ b/actioncable/lib/action_cable/connection/client_socket.rb @@ -21,7 +21,7 @@ module ActionCable return true if env["HTTP_X_FORWARDED_PROTO"] == "https" return true if env["rack.url_scheme"] == "https" - return false + false end CONNECTING = 0 @@ -83,7 +83,7 @@ module ActionCable when Numeric then @driver.text(message.to_s) when String then @driver.text(message) when Array then @driver.binary(message) - else false + else false end end diff --git a/actioncable/lib/action_cable/connection/identification.rb b/actioncable/lib/action_cable/connection/identification.rb index 4b5f9ca115..cc544685dd 100644 --- a/actioncable/lib/action_cable/connection/identification.rb +++ b/actioncable/lib/action_cable/connection/identification.rb @@ -11,7 +11,7 @@ module ActionCable class_attribute :identifiers, default: Set.new end - class_methods do + module ClassMethods # Mark a key as being a connection identifier index that can then be used to find the specific connection again later. # Common identifiers are current_user and current_account, but could be anything, really. # diff --git a/actioncable/lib/action_cable/connection/message_buffer.rb b/actioncable/lib/action_cable/connection/message_buffer.rb index f151a47072..965841b67e 100644 --- a/actioncable/lib/action_cable/connection/message_buffer.rb +++ b/actioncable/lib/action_cable/connection/message_buffer.rb @@ -30,13 +30,10 @@ module ActionCable receive_buffered_messages end - # TODO Change this to private once we've dropped Ruby 2.2 support. - # Workaround for Ruby 2.2 "private attribute?" warning. - protected + private attr_reader :connection attr_reader :buffered_messages - private def valid?(message) message.is_a?(String) end diff --git a/actioncable/lib/action_cable/connection/subscriptions.rb b/actioncable/lib/action_cable/connection/subscriptions.rb index faafd6d0a6..1ad8d05107 100644 --- a/actioncable/lib/action_cable/connection/subscriptions.rb +++ b/actioncable/lib/action_cable/connection/subscriptions.rb @@ -43,7 +43,7 @@ module ActionCable def remove(data) logger.info "Unsubscribing from channel: #{data['identifier']}" - remove_subscription subscriptions[data["identifier"]] + remove_subscription find(data) end def remove_subscription(subscription) @@ -63,12 +63,8 @@ module ActionCable subscriptions.each { |id, channel| remove_subscription(channel) } end - # TODO Change this to private once we've dropped Ruby 2.2 support. - # Workaround for Ruby 2.2 "private attribute?" warning. - protected - attr_reader :connection, :subscriptions - private + attr_reader :connection, :subscriptions delegate :logger, to: :connection def find(data) diff --git a/actioncable/lib/action_cable/connection/web_socket.rb b/actioncable/lib/action_cable/connection/web_socket.rb index 81233ace34..31f29fdd2f 100644 --- a/actioncable/lib/action_cable/connection/web_socket.rb +++ b/actioncable/lib/action_cable/connection/web_socket.rb @@ -34,9 +34,7 @@ module ActionCable websocket.rack_response end - # TODO Change this to private once we've dropped Ruby 2.2 support. - # Workaround for Ruby 2.2 "private attribute?" warning. - protected + private attr_reader :websocket end end diff --git a/actioncable/lib/action_cable/engine.rb b/actioncable/lib/action_cable/engine.rb index c961f47342..53cbb597cd 100644 --- a/actioncable/lib/action_cable/engine.rb +++ b/actioncable/lib/action_cable/engine.rb @@ -2,7 +2,7 @@ require "rails" require "action_cable" -require_relative "helpers/action_cable_helper" +require "action_cable/helpers/action_cable_helper" require "active_support/core_ext/hash/indifferent_access" module ActionCable diff --git a/actioncable/lib/action_cable/gem_version.rb b/actioncable/lib/action_cable/gem_version.rb index af8277d06e..cd1d9bccef 100644 --- a/actioncable/lib/action_cable/gem_version.rb +++ b/actioncable/lib/action_cable/gem_version.rb @@ -7,8 +7,8 @@ module ActionCable end module VERSION - MAJOR = 5 - MINOR = 2 + MAJOR = 6 + MINOR = 0 TINY = 0 PRE = "alpha" diff --git a/actioncable/lib/action_cable/server/configuration.rb b/actioncable/lib/action_cable/server/configuration.rb index 82fed81a18..26209537df 100644 --- a/actioncable/lib/action_cable/server/configuration.rb +++ b/actioncable/lib/action_cable/server/configuration.rb @@ -25,13 +25,26 @@ module ActionCable # Also makes sure proper dependencies are required. def pubsub_adapter adapter = (cable.fetch("adapter") { "redis" }) + + # Require the adapter itself and give useful feedback about + # 1. Missing adapter gems and + # 2. Adapter gems' missing dependencies. path_to_adapter = "action_cable/subscription_adapter/#{adapter}" begin require path_to_adapter - rescue Gem::LoadError => e - raise Gem::LoadError, "Specified '#{adapter}' for Action Cable pubsub adapter, but the gem is not loaded. Add `gem '#{e.name}'` to your Gemfile (and ensure its version is at the minimum required by Action Cable)." rescue LoadError => e - raise LoadError, "Could not load '#{path_to_adapter}'. Make sure that the adapter in config/cable.yml is valid. If you use an adapter other than 'postgresql' or 'redis' add the necessary adapter gem to the Gemfile.", e.backtrace + # We couldn't require the adapter itself. Raise an exception that + # points out config typos and missing gems. + if e.path == path_to_adapter + # We can assume that a non-builtin adapter was specified, so it's + # either misspelled or missing from Gemfile. + raise e.class, "Could not load the '#{adapter}' Action Cable pubsub adapter. Ensure that the adapter is spelled correctly in config/cable.yml and that you've added the necessary adapter gem to your Gemfile.", e.backtrace + + # Bubbled up from the adapter require. Prefix the exception message + # with some guidance about how to address it and reraise. + else + raise e.class, "Error loading the '#{adapter}' Action Cable pubsub adapter. Missing a gem it depends on? #{e.message}", e.backtrace + end end adapter = adapter.camelize diff --git a/actioncable/lib/action_cable/subscription_adapter/async.rb b/actioncable/lib/action_cable/subscription_adapter/async.rb index 96c18c4a2f..c9930299c7 100644 --- a/actioncable/lib/action_cable/subscription_adapter/async.rb +++ b/actioncable/lib/action_cable/subscription_adapter/async.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require_relative "inline" +require "action_cable/subscription_adapter/inline" module ActionCable module SubscriptionAdapter diff --git a/actioncable/lib/action_cable/subscription_adapter/evented_redis.rb b/actioncable/lib/action_cable/subscription_adapter/evented_redis.rb deleted file mode 100644 index 07774810ce..0000000000 --- a/actioncable/lib/action_cable/subscription_adapter/evented_redis.rb +++ /dev/null @@ -1,89 +0,0 @@ -# frozen_string_literal: true - -require "thread" - -gem "em-hiredis", "~> 0.3.0" -gem "redis", "~> 3.0" -require "em-hiredis" -require "redis" - -EventMachine.epoll if EventMachine.epoll? -EventMachine.kqueue if EventMachine.kqueue? - -module ActionCable - module SubscriptionAdapter - class EventedRedis < Base # :nodoc: - prepend ChannelPrefix - - @@mutex = Mutex.new - - # Overwrite this factory method for EventMachine Redis connections if you want to use a different Redis connection library than EM::Hiredis. - # This is needed, for example, when using Makara proxies for distributed Redis. - cattr_accessor :em_redis_connector, default: ->(config) { EM::Hiredis.connect(config[:url]) } - - # Overwrite this factory method for Redis connections if you want to use a different Redis connection library than Redis. - # This is needed, for example, when using Makara proxies for distributed Redis. - cattr_accessor :redis_connector, default: ->(config) { ::Redis.new(url: config[:url]) } - - def initialize(*) - ActiveSupport::Deprecation.warn(<<-MSG.squish) - The "evented_redis" subscription adapter is deprecated and - will be removed in Rails 5.2. Please use the "redis" adapter - instead. - MSG - - super - @redis_connection_for_broadcasts = @redis_connection_for_subscriptions = nil - end - - def broadcast(channel, payload) - redis_connection_for_broadcasts.publish(channel, payload) - end - - def subscribe(channel, message_callback, success_callback = nil) - redis_connection_for_subscriptions.pubsub.subscribe(channel, &message_callback).tap do |result| - result.callback { |reply| success_callback.call } if success_callback - end - end - - def unsubscribe(channel, message_callback) - redis_connection_for_subscriptions.pubsub.unsubscribe_proc(channel, message_callback) - end - - def shutdown - redis_connection_for_subscriptions.pubsub.close_connection - @redis_connection_for_subscriptions = nil - end - - private - def redis_connection_for_subscriptions - ensure_reactor_running - @redis_connection_for_subscriptions || @server.mutex.synchronize do - @redis_connection_for_subscriptions ||= self.class.em_redis_connector.call(@server.config.cable).tap do |redis| - redis.on(:reconnect_failed) do - @logger.error "[ActionCable] Redis reconnect failed." - end - - redis.on(:failed) do - @logger.error "[ActionCable] Redis connection has failed." - end - end - end - end - - def redis_connection_for_broadcasts - @redis_connection_for_broadcasts || @server.mutex.synchronize do - @redis_connection_for_broadcasts ||= self.class.redis_connector.call(@server.config.cable) - end - end - - def ensure_reactor_running - return if EventMachine.reactor_running? && EventMachine.reactor_thread - @@mutex.synchronize do - Thread.new { EventMachine.run } unless EventMachine.reactor_running? - Thread.pass until EventMachine.reactor_running? && EventMachine.reactor_thread - end - end - end - end -end diff --git a/actioncable/lib/action_cable/subscription_adapter/postgresql.rb b/actioncable/lib/action_cable/subscription_adapter/postgresql.rb index a9c0949950..e384ea4afb 100644 --- a/actioncable/lib/action_cable/subscription_adapter/postgresql.rb +++ b/actioncable/lib/action_cable/subscription_adapter/postgresql.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -gem "pg", "~> 0.18" +gem "pg", ">= 0.18", "< 2.0" require "pg" require "thread" require "digest/sha1" diff --git a/actioncable/lib/action_cable/subscription_adapter/redis.rb b/actioncable/lib/action_cable/subscription_adapter/redis.rb index facea944ff..c28951608f 100644 --- a/actioncable/lib/action_cable/subscription_adapter/redis.rb +++ b/actioncable/lib/action_cable/subscription_adapter/redis.rb @@ -2,7 +2,7 @@ require "thread" -gem "redis", "~> 3.0" +gem "redis", ">= 3", "< 5" require "redis" module ActionCable @@ -76,7 +76,7 @@ module ActionCable def listen(conn) conn.without_reconnect do - original_client = conn.client + original_client = conn.respond_to?(:_client) ? conn._client : conn.client conn.subscribe("_action_cable_internal") do |on| on.subscribe do |chan, count| diff --git a/actioncable/lib/rails/generators/channel/channel_generator.rb b/actioncable/lib/rails/generators/channel/channel_generator.rb index c3528370c6..427eef1f55 100644 --- a/actioncable/lib/rails/generators/channel/channel_generator.rb +++ b/actioncable/lib/rails/generators/channel/channel_generator.rb @@ -27,7 +27,7 @@ module Rails private def file_name - @_file_name ||= super.gsub(/_channel/i, "") + @_file_name ||= super.sub(/_channel\z/i, "") end # FIXME: Change these files to symlinks once RubyGems 2.5.0 is required. diff --git a/actioncable/lib/rails/generators/channel/templates/application_cable/channel.rb b/actioncable/lib/rails/generators/channel/templates/application_cable/channel.rb.tt index d672697283..d672697283 100644 --- a/actioncable/lib/rails/generators/channel/templates/application_cable/channel.rb +++ b/actioncable/lib/rails/generators/channel/templates/application_cable/channel.rb.tt diff --git a/actioncable/lib/rails/generators/channel/templates/application_cable/connection.rb b/actioncable/lib/rails/generators/channel/templates/application_cable/connection.rb.tt index 0ff5442f47..0ff5442f47 100644 --- a/actioncable/lib/rails/generators/channel/templates/application_cable/connection.rb +++ b/actioncable/lib/rails/generators/channel/templates/application_cable/connection.rb.tt diff --git a/actioncable/lib/rails/generators/channel/templates/assets/cable.js b/actioncable/lib/rails/generators/channel/templates/assets/cable.js.tt index 739aa5f022..739aa5f022 100644 --- a/actioncable/lib/rails/generators/channel/templates/assets/cable.js +++ b/actioncable/lib/rails/generators/channel/templates/assets/cable.js.tt diff --git a/actioncable/lib/rails/generators/channel/templates/assets/channel.coffee b/actioncable/lib/rails/generators/channel/templates/assets/channel.coffee.tt index 5467811aba..5467811aba 100644 --- a/actioncable/lib/rails/generators/channel/templates/assets/channel.coffee +++ b/actioncable/lib/rails/generators/channel/templates/assets/channel.coffee.tt diff --git a/actioncable/lib/rails/generators/channel/templates/assets/channel.js b/actioncable/lib/rails/generators/channel/templates/assets/channel.js.tt index ab0e68b11a..ab0e68b11a 100644 --- a/actioncable/lib/rails/generators/channel/templates/assets/channel.js +++ b/actioncable/lib/rails/generators/channel/templates/assets/channel.js.tt diff --git a/actioncable/lib/rails/generators/channel/templates/channel.rb b/actioncable/lib/rails/generators/channel/templates/channel.rb.tt index 4bcfb2be4d..4bcfb2be4d 100644 --- a/actioncable/lib/rails/generators/channel/templates/channel.rb +++ b/actioncable/lib/rails/generators/channel/templates/channel.rb.tt diff --git a/actioncable/package.json b/actioncable/package.json index acec1e2e9c..d9df46043d 100644 --- a/actioncable/package.json +++ b/actioncable/package.json @@ -1,6 +1,6 @@ { "name": "actioncable", - "version": "5.2.0-alpha", + "version": "6.0.0-alpha", "description": "WebSocket framework for Ruby on Rails.", "main": "lib/assets/compiled/action_cable.js", "files": [ diff --git a/actioncable/test/channel/base_test.rb b/actioncable/test/channel/base_test.rb index 866bd7c21b..3b8eb63975 100644 --- a/actioncable/test/channel/base_test.rb +++ b/actioncable/test/channel/base_test.rb @@ -97,12 +97,12 @@ class ActionCable::Channel::BaseTest < ActiveSupport::TestCase @channel.subscribe_to_channel assert @channel.room - assert @channel.subscribed? + assert_predicate @channel, :subscribed? @channel.unsubscribe_from_channel - assert ! @channel.room - assert ! @channel.subscribed? + assert_not @channel.room + assert_not_predicate @channel, :subscribed? end test "connection identifiers" do diff --git a/actioncable/test/channel/stream_test.rb b/actioncable/test/channel/stream_test.rb index eca06fe365..53dd7e5b77 100644 --- a/actioncable/test/channel/stream_test.rb +++ b/actioncable/test/channel/stream_test.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require "test_helper" +require "active_support/testing/method_call_assertions" require "stubs/test_connection" require "stubs/room" @@ -143,6 +144,8 @@ module ActionCable::StreamTests end class StreamFromTest < ActionCable::TestCase + include ActiveSupport::Testing::MethodCallAssertions + setup do @server = TestServer.new(subscription_adapter: ActionCable::SubscriptionAdapter::Async) @server.config.allowed_request_origins = %w( http://rubyonrails.com ) @@ -153,10 +156,11 @@ module ActionCable::StreamTests connection = open_connection subscribe_to connection, identifiers: { id: 1 } - connection.websocket.expects(:transmit) - @server.broadcast "test_room_1", { foo: "bar" }, { coder: DummyEncoder } - wait_for_async - wait_for_executor connection.server.worker_pool.executor + assert_called(connection.websocket, :transmit) do + @server.broadcast "test_room_1", { foo: "bar" }, { coder: DummyEncoder } + wait_for_async + wait_for_executor connection.server.worker_pool.executor + end end end @@ -167,7 +171,7 @@ module ActionCable::StreamTests @server.broadcast "channel", {} wait_for_async - refute Thread.current[:ran_callback], "User callback was not run through the worker pool" + assert_not Thread.current[:ran_callback], "User callback was not run through the worker pool" end end @@ -175,10 +179,10 @@ module ActionCable::StreamTests run_in_eventmachine do connection = open_connection expected = { "identifier" => { "channel" => MultiChatChannel.name }.to_json, "type" => "confirm_subscription" } - connection.websocket.expects(:transmit).with(expected.to_json) - receive(connection, command: "subscribe", channel: MultiChatChannel.name, identifiers: {}) - - wait_for_async + assert_called(connection.websocket, :transmit, [expected.to_json]) do + receive(connection, command: "subscribe", channel: MultiChatChannel.name, identifiers: {}) + wait_for_async + end end end @@ -192,15 +196,15 @@ module ActionCable::StreamTests Connection.new(@server, env).tap do |connection| connection.process - assert connection.websocket.possible? + assert_predicate connection.websocket, :possible? wait_for_async - assert connection.websocket.alive? + assert_predicate connection.websocket, :alive? end end def receive(connection, command:, identifiers:, channel: "ActionCable::StreamTests::ChatChannel") - identifier = JSON.generate(channel: channel, **identifiers) + identifier = JSON.generate(identifiers.merge(channel: channel)) connection.dispatch_websocket_message JSON.generate(command: command, identifier: identifier) wait_for_async end diff --git a/actioncable/test/client_test.rb b/actioncable/test/client_test.rb index 56b3ef143b..92fe59c803 100644 --- a/actioncable/test/client_test.rb +++ b/actioncable/test/client_test.rb @@ -7,6 +7,7 @@ require "websocket-client-simple" require "json" require "active_support/hash_with_indifferent_access" +require "active_support/testing/method_call_assertions" #### # 😷 Warning suppression 😷 @@ -27,6 +28,8 @@ WebSocket::Frame::Data.prepend Module.new { #### class ClientTest < ActionCable::TestCase + include ActiveSupport::Testing::MethodCallAssertions + WAIT_WHEN_EXPECTING_EVENT = 2 WAIT_WHEN_NOT_EXPECTING_EVENT = 0.5 @@ -289,9 +292,10 @@ class ClientTest < ActionCable::TestCase subscriptions = app.connections.first.subscriptions.send(:subscriptions) assert_not_equal 0, subscriptions.size, "Missing EchoChannel subscription" channel = subscriptions.first[1] - channel.expects(:unsubscribed) - c.close - sleep 0.1 # Data takes a moment to process + assert_called(channel, :unsubscribed) do + c.close + sleep 0.1 # Data takes a moment to process + end # All data is removed: No more connection or subscription information! assert_equal(0, app.connections.count) @@ -307,7 +311,7 @@ class ClientTest < ActionCable::TestCase ActionCable.server.restart c.wait_for_close - assert c.closed? + assert_predicate c, :closed? end end end diff --git a/actioncable/test/connection/authorization_test.rb b/actioncable/test/connection/authorization_test.rb index 7d039336b8..be41d510ff 100644 --- a/actioncable/test/connection/authorization_test.rb +++ b/actioncable/test/connection/authorization_test.rb @@ -1,9 +1,12 @@ # frozen_string_literal: true require "test_helper" +require "active_support/testing/method_call_assertions" require "stubs/test_server" class ActionCable::Connection::AuthorizationTest < ActionCable::TestCase + include ActiveSupport::Testing::MethodCallAssertions + class Connection < ActionCable::Connection::Base attr_reader :websocket @@ -25,9 +28,9 @@ class ActionCable::Connection::AuthorizationTest < ActionCable::TestCase "HTTP_HOST" => "localhost", "HTTP_ORIGIN" => "http://rubyonrails.com" connection = Connection.new(server, env) - connection.websocket.expects(:close) - - connection.process + assert_called(connection.websocket, :close) do + connection.process + end end end end diff --git a/actioncable/test/connection/base_test.rb b/actioncable/test/connection/base_test.rb index 99488e38c8..ed62e90b70 100644 --- a/actioncable/test/connection/base_test.rb +++ b/actioncable/test/connection/base_test.rb @@ -3,8 +3,11 @@ require "test_helper" require "stubs/test_server" require "active_support/core_ext/object/json" +require "active_support/testing/method_call_assertions" class ActionCable::Connection::BaseTest < ActionCable::TestCase + include ActiveSupport::Testing::MethodCallAssertions + class Connection < ActionCable::Connection::Base attr_reader :websocket, :subscriptions, :message_buffer, :connected @@ -39,10 +42,10 @@ class ActionCable::Connection::BaseTest < ActionCable::TestCase connection = open_connection connection.process - assert connection.websocket.possible? + assert_predicate connection.websocket, :possible? wait_for_async - assert connection.websocket.alive? + assert_predicate connection.websocket, :alive? end end @@ -59,11 +62,12 @@ class ActionCable::Connection::BaseTest < ActionCable::TestCase run_in_eventmachine do connection = open_connection - connection.websocket.expects(:transmit).with({ type: "welcome" }.to_json) - connection.message_buffer.expects(:process!) - - connection.process - wait_for_async + assert_called_with(connection.websocket, :transmit, [{ type: "welcome" }.to_json]) do + assert_called(connection.message_buffer, :process!) do + connection.process + wait_for_async + end + end assert_equal [ connection ], @server.connections assert connection.connected @@ -80,10 +84,11 @@ class ActionCable::Connection::BaseTest < ActionCable::TestCase connection.send :handle_open assert connection.connected - connection.subscriptions.expects(:unsubscribe_from_all) - connection.send :handle_close + assert_called(connection.subscriptions, :unsubscribe_from_all) do + connection.send :handle_close + end - assert ! connection.connected + assert_not connection.connected assert_equal [], @server.connections end end @@ -95,7 +100,7 @@ class ActionCable::Connection::BaseTest < ActionCable::TestCase statistics = connection.statistics - assert statistics[:identifier].blank? + assert_predicate statistics[:identifier], :blank? assert_kind_of Time, statistics[:started_at] assert_equal [], statistics[:subscriptions] end @@ -106,8 +111,9 @@ class ActionCable::Connection::BaseTest < ActionCable::TestCase connection = open_connection connection.process - connection.websocket.expects(:close) - connection.close + assert_called(connection.websocket, :close) do + connection.close + end end end diff --git a/actioncable/test/connection/client_socket_test.rb b/actioncable/test/connection/client_socket_test.rb index 2051216010..da72501c8e 100644 --- a/actioncable/test/connection/client_socket_test.rb +++ b/actioncable/test/connection/client_socket_test.rb @@ -2,8 +2,11 @@ require "test_helper" require "stubs/test_server" +require "active_support/testing/method_call_assertions" class ActionCable::Connection::ClientSocketTest < ActionCable::TestCase + include ActiveSupport::Testing::MethodCallAssertions + class Connection < ActionCable::Connection::Base attr_reader :connected, :websocket, :errors @@ -41,9 +44,10 @@ class ActionCable::Connection::ClientSocketTest < ActionCable::TestCase # Internal hax = :( client = connection.websocket.send(:websocket) client.instance_variable_get("@stream").expects(:write).raises("foo") - client.expects(:client_gone).never - client.write("boo") + assert_not_called(client, :client_gone) do + client.write("boo") + end assert_equal %w[ foo ], connection.errors end end @@ -67,9 +71,9 @@ class ActionCable::Connection::ClientSocketTest < ActionCable::TestCase env = Rack::MockRequest.env_for "/test", "HTTP_CONNECTION" => "upgrade", "HTTP_UPGRADE" => "websocket", "HTTP_HOST" => "localhost", "HTTP_ORIGIN" => "http://rubyonrails.com" - io = \ + io, client_io = \ begin - Socket.pair(Socket::AF_UNIX, Socket::SOCK_STREAM, 0).first + Socket.pair(Socket::AF_UNIX, Socket::SOCK_STREAM, 0) rescue StringIO.new end @@ -77,6 +81,14 @@ class ActionCable::Connection::ClientSocketTest < ActionCable::TestCase Connection.new(@server, env).tap do |connection| connection.process + if client_io + # Make sure server returns handshake response + Timeout.timeout(1) do + loop do + break if client_io.readline == "\r\n" + end + end + end connection.send :handle_open assert connection.connected end diff --git a/actioncable/test/connection/identifier_test.rb b/actioncable/test/connection/identifier_test.rb index 6b6c8cd31a..de1ae1d5b9 100644 --- a/actioncable/test/connection/identifier_test.rb +++ b/actioncable/test/connection/identifier_test.rb @@ -1,10 +1,13 @@ # frozen_string_literal: true require "test_helper" +require "active_support/testing/method_call_assertions" require "stubs/test_server" require "stubs/user" class ActionCable::Connection::IdentifierTest < ActionCable::TestCase + include ActiveSupport::Testing::MethodCallAssertions + class Connection < ActionCable::Connection::Base identified_by :current_user attr_reader :websocket @@ -41,8 +44,9 @@ class ActionCable::Connection::IdentifierTest < ActionCable::TestCase run_in_eventmachine do open_connection_with_stubbed_pubsub - @connection.websocket.expects(:close) - @connection.process_internal_message "type" => "disconnect" + assert_called(@connection.websocket, :close) do + @connection.process_internal_message "type" => "disconnect" + end end end @@ -50,8 +54,9 @@ class ActionCable::Connection::IdentifierTest < ActionCable::TestCase run_in_eventmachine do open_connection_with_stubbed_pubsub - @connection.websocket.expects(:close).never - @connection.process_internal_message "type" => "unknown" + assert_not_called(@connection.websocket, :close) do + @connection.process_internal_message "type" => "unknown" + end end end diff --git a/actioncable/test/connection/stream_test.rb b/actioncable/test/connection/stream_test.rb index b0419b0994..1e1466af31 100644 --- a/actioncable/test/connection/stream_test.rb +++ b/actioncable/test/connection/stream_test.rb @@ -1,9 +1,12 @@ # frozen_string_literal: true require "test_helper" +require "active_support/testing/method_call_assertions" require "stubs/test_server" class ActionCable::Connection::StreamTest < ActionCable::TestCase + include ActiveSupport::Testing::MethodCallAssertions + class Connection < ActionCable::Connection::Base attr_reader :connected, :websocket, :errors @@ -42,9 +45,10 @@ class ActionCable::Connection::StreamTest < ActionCable::TestCase # Internal hax = :( client = connection.websocket.send(:websocket) client.instance_variable_get("@stream").instance_variable_get("@rack_hijack_io").expects(:write).raises(closed_exception, "foo") - client.expects(:client_gone) - client.write("boo") + assert_called(client, :client_gone) do + client.write("boo") + end assert_equal [], connection.errors end end diff --git a/actioncable/test/connection/subscriptions_test.rb b/actioncable/test/connection/subscriptions_test.rb index 149a40604a..7bc8c4241c 100644 --- a/actioncable/test/connection/subscriptions_test.rb +++ b/actioncable/test/connection/subscriptions_test.rb @@ -1,8 +1,11 @@ # frozen_string_literal: true require "test_helper" +require "active_support/testing/method_call_assertions" class ActionCable::Connection::SubscriptionsTest < ActionCable::TestCase + include ActiveSupport::Testing::MethodCallAssertions + class Connection < ActionCable::Connection::Base attr_reader :websocket @@ -45,7 +48,7 @@ class ActionCable::Connection::SubscriptionsTest < ActionCable::TestCase setup_connection @subscriptions.execute_command "command" => "subscribe" - assert @subscriptions.identifiers.empty? + assert_empty @subscriptions.identifiers end end @@ -55,10 +58,12 @@ class ActionCable::Connection::SubscriptionsTest < ActionCable::TestCase subscribe_to_chat_channel channel = subscribe_to_chat_channel - channel.expects(:unsubscribe_from_channel) - @subscriptions.execute_command "command" => "unsubscribe", "identifier" => @chat_identifier - assert @subscriptions.identifiers.empty? + assert_called(channel, :unsubscribe_from_channel) do + @subscriptions.execute_command "command" => "unsubscribe", "identifier" => @chat_identifier + end + + assert_empty @subscriptions.identifiers end end @@ -67,7 +72,7 @@ class ActionCable::Connection::SubscriptionsTest < ActionCable::TestCase setup_connection @subscriptions.execute_command "command" => "unsubscribe" - assert @subscriptions.identifiers.empty? + assert_empty @subscriptions.identifiers end end @@ -92,10 +97,11 @@ class ActionCable::Connection::SubscriptionsTest < ActionCable::TestCase channel2_id = ActiveSupport::JSON.encode(id: 2, channel: "ActionCable::Connection::SubscriptionsTest::ChatChannel") channel2 = subscribe_to_chat_channel(channel2_id) - channel1.expects(:unsubscribe_from_channel) - channel2.expects(:unsubscribe_from_channel) - - @subscriptions.unsubscribe_from_all + assert_called(channel1, :unsubscribe_from_channel) do + assert_called(channel2, :unsubscribe_from_channel) do + @subscriptions.unsubscribe_from_all + end + end end end diff --git a/actioncable/test/server/base_test.rb b/actioncable/test/server/base_test.rb index 1312e45f49..3b5931f0a4 100644 --- a/actioncable/test/server/base_test.rb +++ b/actioncable/test/server/base_test.rb @@ -3,8 +3,11 @@ require "test_helper" require "stubs/test_server" require "active_support/core_ext/hash/indifferent_access" +require "active_support/testing/method_call_assertions" class BaseTest < ActiveSupport::TestCase + include ActiveSupport::Testing::MethodCallAssertions + def setup @server = ActionCable::Server::Base.new @server.config.cable = { adapter: "async" }.with_indifferent_access @@ -19,17 +22,20 @@ class BaseTest < ActiveSupport::TestCase conn = FakeConnection.new @server.add_connection(conn) - conn.expects(:close) - @server.restart + assert_called(conn, :close) do + @server.restart + end end test "#restart shuts down worker pool" do - @server.worker_pool.expects(:halt) - @server.restart + assert_called(@server.worker_pool, :halt) do + @server.restart + end end test "#restart shuts down pub/sub adapter" do - @server.pubsub.expects(:shutdown) - @server.restart + assert_called(@server.pubsub, :shutdown) do + @server.restart + end end end diff --git a/actioncable/test/subscription_adapter/common.rb b/actioncable/test/subscription_adapter/common.rb index c533a9f3eb..b3e9ae9d5c 100644 --- a/actioncable/test/subscription_adapter/common.rb +++ b/actioncable/test/subscription_adapter/common.rb @@ -32,7 +32,7 @@ module CommonSubscriptionAdapterTest subscribed = Concurrent::Event.new adapter.subscribe(channel, callback, Proc.new { subscribed.set }) subscribed.wait(WAIT_WHEN_EXPECTING_EVENT) - assert subscribed.set? + assert_predicate subscribed, :set? yield queue diff --git a/actioncable/test/subscription_adapter/evented_redis_test.rb b/actioncable/test/subscription_adapter/evented_redis_test.rb deleted file mode 100644 index e3e0a0c72a..0000000000 --- a/actioncable/test/subscription_adapter/evented_redis_test.rb +++ /dev/null @@ -1,61 +0,0 @@ -# frozen_string_literal: true - -require "test_helper" -require_relative "common" -require_relative "channel_prefix" - -class EventedRedisAdapterTest < ActionCable::TestCase - include CommonSubscriptionAdapterTest - include ChannelPrefixTest - - def setup - assert_deprecated do - super - end - - # em-hiredis is warning-rich - @previous_verbose, $VERBOSE = $VERBOSE, nil - end - - def teardown - super - - # Ensure EM is shut down before we re-enable warnings - EventMachine.reactor_thread.tap do |thread| - EventMachine.stop - thread.join - end - - $VERBOSE = @previous_verbose - end - - def test_slow_eventmachine - require "eventmachine" - require "thread" - - lock = Mutex.new - - EventMachine.singleton_class.class_eval do - alias_method :delayed_initialize_event_machine, :initialize_event_machine - define_method(:initialize_event_machine) do - lock.synchronize do - sleep 0.5 - delayed_initialize_event_machine - end - end - end - - test_basic_broadcast - ensure - lock.synchronize do - EventMachine.singleton_class.class_eval do - alias_method :initialize_event_machine, :delayed_initialize_event_machine - remove_method :delayed_initialize_event_machine - end - end - end - - def cable_config - { adapter: "evented_redis", url: "redis://:password@127.0.0.1:6379/12" } - end -end diff --git a/actioncable/test/subscription_adapter/redis_test.rb b/actioncable/test/subscription_adapter/redis_test.rb index 69120d5753..63823d6ef0 100644 --- a/actioncable/test/subscription_adapter/redis_test.rb +++ b/actioncable/test/subscription_adapter/redis_test.rb @@ -4,12 +4,15 @@ require "test_helper" require_relative "common" require_relative "channel_prefix" +require "active_support/testing/method_call_assertions" +require "action_cable/subscription_adapter/redis" + class RedisAdapterTest < ActionCable::TestCase include CommonSubscriptionAdapterTest include ChannelPrefixTest def cable_config - { adapter: "redis", driver: "ruby", url: "redis://:password@127.0.0.1:6379/12" } + { adapter: "redis", driver: "ruby" } end end @@ -23,6 +26,22 @@ class RedisAdapterTest::AlternateConfiguration < RedisAdapterTest def cable_config alt_cable_config = super.dup alt_cable_config.delete(:url) - alt_cable_config.merge(host: "127.0.0.1", port: 6379, db: 12, password: "password") + alt_cable_config.merge(host: "127.0.0.1", port: 6379, db: 12) + end +end + +class RedisAdapterTest::Connector < ActiveSupport::TestCase + include ActiveSupport::Testing::MethodCallAssertions + + test "slices url, host, port, db, and password from config" do + config = { url: 1, host: 2, port: 3, db: 4, password: 5 } + + assert_called_with ::Redis, :new, [ config ] do + connect config.merge(other: "unrelated", stuff: "here") + end + end + + def connect(config) + ActionCable::SubscriptionAdapter::Redis.redis_connector.call(config) end end diff --git a/actioncable/test/test_helper.rb b/actioncable/test/test_helper.rb index 2a4611fb37..2f186b7193 100644 --- a/actioncable/test/test_helper.rb +++ b/actioncable/test/test_helper.rb @@ -4,7 +4,7 @@ require "action_cable" require "active_support/testing/autorun" require "puma" -require "mocha/setup" +require "mocha/minitest" require "rack/mock" begin |