mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
Use websocket-client-simple instead of Faye as a websockets client
Mostly, this is just to avoid EventMachine. But there's also an argument to be made that we're better off using a different protocol library for our test suite than the one we use to implement the server.
This commit is contained in:
parent
a5abc310cd
commit
7c812c2401
3 changed files with 89 additions and 65 deletions
5
Gemfile
5
Gemfile
|
@ -72,10 +72,7 @@ group :cable do
|
||||||
gem "hiredis", require: false
|
gem "hiredis", require: false
|
||||||
gem "redis", require: false
|
gem "redis", require: false
|
||||||
|
|
||||||
gem "faye-websocket", require: false
|
gem "websocket-client-simple", require: false
|
||||||
|
|
||||||
# Lock to 1.1.1 until the fix for https://github.com/faye/faye/issues/394 is released
|
|
||||||
gem "faye", "1.1.1", require: false
|
|
||||||
|
|
||||||
gem "blade", require: false, platforms: [:ruby]
|
gem "blade", require: false, platforms: [:ruby]
|
||||||
gem "blade-sauce_labs_plugin", require: false, platforms: [:ruby]
|
gem "blade-sauce_labs_plugin", require: false, platforms: [:ruby]
|
||||||
|
|
|
@ -170,6 +170,7 @@ GEM
|
||||||
em-socksify (0.3.1)
|
em-socksify (0.3.1)
|
||||||
eventmachine (>= 1.0.0.beta.4)
|
eventmachine (>= 1.0.0.beta.4)
|
||||||
erubis (2.7.0)
|
erubis (2.7.0)
|
||||||
|
event_emitter (0.2.5)
|
||||||
eventmachine (1.2.0.1)
|
eventmachine (1.2.0.1)
|
||||||
eventmachine (1.2.0.1-x64-mingw32)
|
eventmachine (1.2.0.1-x64-mingw32)
|
||||||
eventmachine (1.2.0.1-x86-mingw32)
|
eventmachine (1.2.0.1-x86-mingw32)
|
||||||
|
@ -346,6 +347,9 @@ GEM
|
||||||
nokogiri
|
nokogiri
|
||||||
wdm (0.1.1)
|
wdm (0.1.1)
|
||||||
websocket (1.2.3)
|
websocket (1.2.3)
|
||||||
|
websocket-client-simple (0.3.0)
|
||||||
|
event_emitter
|
||||||
|
websocket
|
||||||
websocket-driver (0.6.4)
|
websocket-driver (0.6.4)
|
||||||
websocket-extensions (>= 0.1.0)
|
websocket-extensions (>= 0.1.0)
|
||||||
websocket-extensions (0.1.2)
|
websocket-extensions (0.1.2)
|
||||||
|
@ -370,8 +374,6 @@ DEPENDENCIES
|
||||||
delayed_job!
|
delayed_job!
|
||||||
delayed_job_active_record!
|
delayed_job_active_record!
|
||||||
em-hiredis
|
em-hiredis
|
||||||
faye (= 1.1.1)
|
|
||||||
faye-websocket
|
|
||||||
hiredis
|
hiredis
|
||||||
jquery-rails
|
jquery-rails
|
||||||
kindlerb (= 0.1.1)
|
kindlerb (= 0.1.1)
|
||||||
|
@ -409,6 +411,7 @@ DEPENDENCIES
|
||||||
uglifier (>= 1.3.0)
|
uglifier (>= 1.3.0)
|
||||||
w3c_validators
|
w3c_validators
|
||||||
wdm (>= 0.1.0)
|
wdm (>= 0.1.0)
|
||||||
|
websocket-client-simple
|
||||||
|
|
||||||
BUNDLED WITH
|
BUNDLED WITH
|
||||||
1.13.1
|
1.13.1
|
||||||
|
|
|
@ -1,13 +1,38 @@
|
||||||
require "test_helper"
|
require "test_helper"
|
||||||
require "concurrent"
|
require "concurrent"
|
||||||
|
|
||||||
require "faye/websocket"
|
require "websocket-client-simple"
|
||||||
require "json"
|
require "json"
|
||||||
|
|
||||||
require "active_support/hash_with_indifferent_access"
|
require "active_support/hash_with_indifferent_access"
|
||||||
|
|
||||||
|
####
|
||||||
|
# 😷 Warning suppression 😷
|
||||||
|
WebSocket::Frame::Handler::Handler03.prepend Module.new {
|
||||||
|
def initialize(*)
|
||||||
|
@application_data_buffer = nil
|
||||||
|
super
|
||||||
|
end
|
||||||
|
}
|
||||||
|
|
||||||
|
WebSocket::Frame::Data.prepend Module.new {
|
||||||
|
def initialize(*)
|
||||||
|
@masking_key = nil
|
||||||
|
super
|
||||||
|
end
|
||||||
|
}
|
||||||
|
|
||||||
|
WebSocket::Client::Simple::Client.prepend Module.new {
|
||||||
|
def initialize(*)
|
||||||
|
@socket = nil
|
||||||
|
super
|
||||||
|
end
|
||||||
|
}
|
||||||
|
#
|
||||||
|
####
|
||||||
|
|
||||||
class ClientTest < ActionCable::TestCase
|
class ClientTest < ActionCable::TestCase
|
||||||
WAIT_WHEN_EXPECTING_EVENT = 8
|
WAIT_WHEN_EXPECTING_EVENT = 2
|
||||||
WAIT_WHEN_NOT_EXPECTING_EVENT = 0.5
|
WAIT_WHEN_NOT_EXPECTING_EVENT = 0.5
|
||||||
|
|
||||||
class EchoChannel < ActionCable::Channel::Base
|
class EchoChannel < ActionCable::Channel::Base
|
||||||
|
@ -42,16 +67,6 @@ class ClientTest < ActionCable::TestCase
|
||||||
|
|
||||||
# and now the "real" setup for our test:
|
# and now the "real" setup for our test:
|
||||||
server.config.disable_request_forgery_protection = true
|
server.config.disable_request_forgery_protection = true
|
||||||
|
|
||||||
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
|
end
|
||||||
|
|
||||||
def with_puma_server(rack_app = ActionCable.server, port = 3099)
|
def with_puma_server(rack_app = ActionCable.server, port = 3099)
|
||||||
|
@ -72,44 +87,49 @@ class ClientTest < ActionCable::TestCase
|
||||||
attr_reader :pings
|
attr_reader :pings
|
||||||
|
|
||||||
def initialize(port)
|
def initialize(port)
|
||||||
@ws = Faye::WebSocket::Client.new("ws://127.0.0.1:#{port}/")
|
messages = @messages = Queue.new
|
||||||
@messages = Queue.new
|
closed = @closed = Concurrent::Event.new
|
||||||
@closed = Concurrent::Event.new
|
has_messages = @has_messages = Concurrent::Semaphore.new(0)
|
||||||
@has_messages = Concurrent::Semaphore.new(0)
|
pings = @pings = Concurrent::AtomicFixnum.new(0)
|
||||||
@pings = 0
|
|
||||||
|
|
||||||
open = Concurrent::Event.new
|
open = Concurrent::Promise.new
|
||||||
error = nil
|
|
||||||
|
|
||||||
@ws.on(:error) do |event|
|
@ws = WebSocket::Client::Simple.connect("ws://127.0.0.1:#{port}/") do |ws|
|
||||||
if open.set?
|
ws.on(:error) do |event|
|
||||||
@messages << RuntimeError.new(event.message)
|
event = RuntimeError.new(event.message) unless event.is_a?(Exception)
|
||||||
else
|
|
||||||
error = event.message
|
if open.pending?
|
||||||
open.set
|
open.fail(event)
|
||||||
|
else
|
||||||
|
messages << event
|
||||||
|
has_messages.release
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
ws.on(:open) do |event|
|
||||||
|
open.set(true)
|
||||||
|
end
|
||||||
|
|
||||||
|
ws.on(:message) do |event|
|
||||||
|
if event.type == :close
|
||||||
|
closed.set
|
||||||
|
else
|
||||||
|
message = JSON.parse(event.data)
|
||||||
|
if message["type"] == "ping"
|
||||||
|
pings.increment
|
||||||
|
else
|
||||||
|
messages << message
|
||||||
|
has_messages.release
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
ws.on(:close) do |event|
|
||||||
|
closed.set
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ws.on(:open) do |event|
|
open.wait!(WAIT_WHEN_EXPECTING_EVENT)
|
||||||
open.set
|
|
||||||
end
|
|
||||||
|
|
||||||
@ws.on(:message) do |event|
|
|
||||||
message = JSON.parse(event.data)
|
|
||||||
if message["type"] == "ping"
|
|
||||||
@pings += 1
|
|
||||||
else
|
|
||||||
@messages << message
|
|
||||||
@has_messages.release
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
@ws.on(:close) do |event|
|
|
||||||
@closed.set
|
|
||||||
end
|
|
||||||
|
|
||||||
open.wait(WAIT_WHEN_EXPECTING_EVENT)
|
|
||||||
raise error if error
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def read_message
|
def read_message
|
||||||
|
@ -160,13 +180,17 @@ class ClientTest < ActionCable::TestCase
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def faye_client(port)
|
def websocket_client(port)
|
||||||
SyncClient.new(port)
|
SyncClient.new(port)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def concurrently(enum)
|
||||||
|
enum.map { |*x| Concurrent::Future.execute { yield(*x) } }.map(&:value!)
|
||||||
|
end
|
||||||
|
|
||||||
def test_single_client
|
def test_single_client
|
||||||
with_puma_server do |port|
|
with_puma_server do |port|
|
||||||
c = faye_client(port)
|
c = websocket_client(port)
|
||||||
assert_equal({ "type" => "welcome" }, c.read_message) # pop the first welcome message off the stack
|
assert_equal({ "type" => "welcome" }, c.read_message) # pop the first welcome message off the stack
|
||||||
c.send_message command: "subscribe", identifier: JSON.generate(channel: "ClientTest::EchoChannel")
|
c.send_message command: "subscribe", identifier: JSON.generate(channel: "ClientTest::EchoChannel")
|
||||||
assert_equal({ "identifier"=>"{\"channel\":\"ClientTest::EchoChannel\"}", "type"=>"confirm_subscription" }, c.read_message)
|
assert_equal({ "identifier"=>"{\"channel\":\"ClientTest::EchoChannel\"}", "type"=>"confirm_subscription" }, c.read_message)
|
||||||
|
@ -178,12 +202,12 @@ class ClientTest < ActionCable::TestCase
|
||||||
|
|
||||||
def test_interacting_clients
|
def test_interacting_clients
|
||||||
with_puma_server do |port|
|
with_puma_server do |port|
|
||||||
clients = 10.times.map { faye_client(port) }
|
clients = concurrently(10.times) { websocket_client(port) }
|
||||||
|
|
||||||
barrier_1 = Concurrent::CyclicBarrier.new(clients.size)
|
barrier_1 = Concurrent::CyclicBarrier.new(clients.size)
|
||||||
barrier_2 = Concurrent::CyclicBarrier.new(clients.size)
|
barrier_2 = Concurrent::CyclicBarrier.new(clients.size)
|
||||||
|
|
||||||
clients.map { |c| Concurrent::Future.execute {
|
concurrently(clients) do |c|
|
||||||
assert_equal({ "type" => "welcome" }, c.read_message) # pop the first welcome message off the stack
|
assert_equal({ "type" => "welcome" }, c.read_message) # pop the first welcome message off the stack
|
||||||
c.send_message command: "subscribe", identifier: JSON.generate(channel: "ClientTest::EchoChannel")
|
c.send_message command: "subscribe", identifier: JSON.generate(channel: "ClientTest::EchoChannel")
|
||||||
assert_equal({ "identifier"=>'{"channel":"ClientTest::EchoChannel"}', "type"=>"confirm_subscription" }, c.read_message)
|
assert_equal({ "identifier"=>'{"channel":"ClientTest::EchoChannel"}', "type"=>"confirm_subscription" }, c.read_message)
|
||||||
|
@ -193,38 +217,38 @@ class ClientTest < ActionCable::TestCase
|
||||||
c.send_message command: "message", identifier: JSON.generate(channel: "ClientTest::EchoChannel"), data: JSON.generate(action: "bulk", message: "hello")
|
c.send_message command: "message", identifier: JSON.generate(channel: "ClientTest::EchoChannel"), data: JSON.generate(action: "bulk", message: "hello")
|
||||||
barrier_2.wait WAIT_WHEN_EXPECTING_EVENT
|
barrier_2.wait WAIT_WHEN_EXPECTING_EVENT
|
||||||
assert_equal clients.size, c.read_messages(clients.size).size
|
assert_equal clients.size, c.read_messages(clients.size).size
|
||||||
} }.each(&:wait!)
|
end
|
||||||
|
|
||||||
clients.map { |c| Concurrent::Future.execute { c.close } }.each(&:wait!)
|
concurrently(clients, &:close)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_many_clients
|
def test_many_clients
|
||||||
with_puma_server do |port|
|
with_puma_server do |port|
|
||||||
clients = 100.times.map { faye_client(port) }
|
clients = concurrently(100.times) { websocket_client(port) }
|
||||||
|
|
||||||
clients.map { |c| Concurrent::Future.execute {
|
concurrently(clients) do |c|
|
||||||
assert_equal({ "type" => "welcome" }, c.read_message) # pop the first welcome message off the stack
|
assert_equal({ "type" => "welcome" }, c.read_message) # pop the first welcome message off the stack
|
||||||
c.send_message command: "subscribe", identifier: JSON.generate(channel: "ClientTest::EchoChannel")
|
c.send_message command: "subscribe", identifier: JSON.generate(channel: "ClientTest::EchoChannel")
|
||||||
assert_equal({ "identifier"=>'{"channel":"ClientTest::EchoChannel"}', "type"=>"confirm_subscription" }, c.read_message)
|
assert_equal({ "identifier"=>'{"channel":"ClientTest::EchoChannel"}', "type"=>"confirm_subscription" }, c.read_message)
|
||||||
c.send_message command: "message", identifier: JSON.generate(channel: "ClientTest::EchoChannel"), data: JSON.generate(action: "ding", message: "hello")
|
c.send_message command: "message", identifier: JSON.generate(channel: "ClientTest::EchoChannel"), data: JSON.generate(action: "ding", message: "hello")
|
||||||
assert_equal({ "identifier"=>'{"channel":"ClientTest::EchoChannel"}', "message"=>{ "dong"=>"hello" } }, c.read_message)
|
assert_equal({ "identifier"=>'{"channel":"ClientTest::EchoChannel"}', "message"=>{ "dong"=>"hello" } }, c.read_message)
|
||||||
} }.each(&:wait!)
|
end
|
||||||
|
|
||||||
clients.map { |c| Concurrent::Future.execute { c.close } }.each(&:wait!)
|
concurrently(clients, &:close)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_disappearing_client
|
def test_disappearing_client
|
||||||
with_puma_server do |port|
|
with_puma_server do |port|
|
||||||
c = faye_client(port)
|
c = websocket_client(port)
|
||||||
assert_equal({ "type" => "welcome" }, c.read_message) # pop the first welcome message off the stack
|
assert_equal({ "type" => "welcome" }, c.read_message) # pop the first welcome message off the stack
|
||||||
c.send_message command: "subscribe", identifier: JSON.generate(channel: "ClientTest::EchoChannel")
|
c.send_message command: "subscribe", identifier: JSON.generate(channel: "ClientTest::EchoChannel")
|
||||||
assert_equal({ "identifier"=>"{\"channel\":\"ClientTest::EchoChannel\"}", "type"=>"confirm_subscription" }, c.read_message)
|
assert_equal({ "identifier"=>"{\"channel\":\"ClientTest::EchoChannel\"}", "type"=>"confirm_subscription" }, c.read_message)
|
||||||
c.send_message command: "message", identifier: JSON.generate(channel: "ClientTest::EchoChannel"), data: JSON.generate(action: "delay", message: "hello")
|
c.send_message command: "message", identifier: JSON.generate(channel: "ClientTest::EchoChannel"), data: JSON.generate(action: "delay", message: "hello")
|
||||||
c.close # disappear before write
|
c.close # disappear before write
|
||||||
|
|
||||||
c = faye_client(port)
|
c = websocket_client(port)
|
||||||
assert_equal({ "type" => "welcome" }, c.read_message) # pop the first welcome message off the stack
|
assert_equal({ "type" => "welcome" }, c.read_message) # pop the first welcome message off the stack
|
||||||
c.send_message command: "subscribe", identifier: JSON.generate(channel: "ClientTest::EchoChannel")
|
c.send_message command: "subscribe", identifier: JSON.generate(channel: "ClientTest::EchoChannel")
|
||||||
assert_equal({ "identifier"=>"{\"channel\":\"ClientTest::EchoChannel\"}", "type"=>"confirm_subscription" }, c.read_message)
|
assert_equal({ "identifier"=>"{\"channel\":\"ClientTest::EchoChannel\"}", "type"=>"confirm_subscription" }, c.read_message)
|
||||||
|
@ -239,7 +263,7 @@ class ClientTest < ActionCable::TestCase
|
||||||
app = ActionCable.server
|
app = ActionCable.server
|
||||||
identifier = JSON.generate(channel: "ClientTest::EchoChannel")
|
identifier = JSON.generate(channel: "ClientTest::EchoChannel")
|
||||||
|
|
||||||
c = faye_client(port)
|
c = websocket_client(port)
|
||||||
assert_equal({ "type" => "welcome" }, c.read_message)
|
assert_equal({ "type" => "welcome" }, c.read_message)
|
||||||
c.send_message command: "subscribe", identifier: identifier
|
c.send_message command: "subscribe", identifier: identifier
|
||||||
assert_equal({ "identifier"=>"{\"channel\":\"ClientTest::EchoChannel\"}", "type"=>"confirm_subscription" }, c.read_message)
|
assert_equal({ "identifier"=>"{\"channel\":\"ClientTest::EchoChannel\"}", "type"=>"confirm_subscription" }, c.read_message)
|
||||||
|
@ -260,7 +284,7 @@ class ClientTest < ActionCable::TestCase
|
||||||
|
|
||||||
def test_server_restart
|
def test_server_restart
|
||||||
with_puma_server do |port|
|
with_puma_server do |port|
|
||||||
c = faye_client(port)
|
c = websocket_client(port)
|
||||||
assert_equal({ "type" => "welcome" }, c.read_message)
|
assert_equal({ "type" => "welcome" }, c.read_message)
|
||||||
c.send_message command: "subscribe", identifier: JSON.generate(channel: "ClientTest::EchoChannel")
|
c.send_message command: "subscribe", identifier: JSON.generate(channel: "ClientTest::EchoChannel")
|
||||||
assert_equal({ "identifier"=>"{\"channel\":\"ClientTest::EchoChannel\"}", "type"=>"confirm_subscription" }, c.read_message)
|
assert_equal({ "identifier"=>"{\"channel\":\"ClientTest::EchoChannel\"}", "type"=>"confirm_subscription" }, c.read_message)
|
||||||
|
|
Loading…
Reference in a new issue