anyt 0.8.5 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/MIT-LICENSE +1 -1
  3. data/README.md +4 -1
  4. data/lib/anyt/cli.rb +81 -14
  5. data/lib/anyt/client.rb +12 -7
  6. data/lib/anyt/command.rb +33 -18
  7. data/lib/anyt/config.rb +15 -7
  8. data/lib/anyt/dummy/application.rb +31 -6
  9. data/lib/anyt/dummy/config.ru +4 -0
  10. data/lib/anyt/dummy/tmp/development_secret.txt +1 -0
  11. data/lib/anyt/ext/minitest.rb +24 -7
  12. data/lib/anyt/remote_control.rb +33 -0
  13. data/lib/anyt/rpc.rb +6 -9
  14. data/lib/anyt/tests.rb +5 -7
  15. data/lib/anyt/tests/core/ping_test.rb +2 -2
  16. data/lib/anyt/tests/core/welcome_test.rb +2 -2
  17. data/lib/anyt/tests/features/channel_state_test.rb +81 -0
  18. data/lib/anyt/tests/features/remote_disconnect_test.rb +30 -0
  19. data/lib/anyt/tests/features/server_restart_test.rb +28 -0
  20. data/lib/anyt/tests/request/channel_test.rb +28 -0
  21. data/lib/anyt/tests/request/connection_test.rb +23 -21
  22. data/lib/anyt/tests/request/disconnect_reasons_test.rb +23 -0
  23. data/lib/anyt/tests/request/disconnection_test.rb +50 -32
  24. data/lib/anyt/tests/streams/broadcast_test.rb +13 -13
  25. data/lib/anyt/tests/streams/multiple_clients_test.rb +13 -13
  26. data/lib/anyt/tests/streams/multiple_test.rb +15 -15
  27. data/lib/anyt/tests/streams/single_test.rb +39 -14
  28. data/lib/anyt/tests/streams/stop_test.rb +57 -0
  29. data/lib/anyt/tests/subscriptions/ack_test.rb +9 -11
  30. data/lib/anyt/tests/subscriptions/params_test.rb +6 -7
  31. data/lib/anyt/tests/subscriptions/perform_test.rb +16 -18
  32. data/lib/anyt/tests/subscriptions/transmissions_test.rb +6 -7
  33. data/lib/anyt/version.rb +1 -1
  34. metadata +38 -74
  35. data/.gitignore +0 -43
  36. data/.rubocop.yml +0 -80
  37. data/Gemfile +0 -6
  38. data/LICENSE.txt +0 -21
  39. data/Makefile +0 -5
  40. data/anyt.gemspec +0 -42
  41. data/circle.yml +0 -14
  42. data/etc/tests/channel_broadcast_test.rb +0 -51
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b45eb7502a2f6155788228c055c05232ad0ab7baf47509e70cb783479047eb07
4
- data.tar.gz: 62266da53d1c2f5a58376fd64b340b7e6b94dac05af6ca16c1580f84723bf177
3
+ metadata.gz: ae6e9315f47d249825321f25325b732f6c772c038897e276be2808c67a6a825c
4
+ data.tar.gz: 0561047ee5e30ee0ad01dd39963d6b637c813fb2edc0a1661314b3079e5ce605
5
5
  SHA512:
6
- metadata.gz: 764bf51ba64a579c4906633cf19d46d156e143b66a642c96a285e6fb862cc34357da75f8c3ec2627afc4cedc3d72ca38d95a69990b53d42fbbfc9d8280b0521b
7
- data.tar.gz: 57611bdee9877c384c651c8e45c928ecc85af77c888cb985c794fe9903735aed4660d3619afc5dd8ff0d191cf8509327baeeda65bfff9a8d73c33357607103c7
6
+ metadata.gz: 8ecf5301765607d5e4b724f77754bd37e0ec8e870a29e5e5e881db1bec7ec2ee14b7715512b5d84fee2e454035da2ae8c4661675d08d49107b7bf59f5b37bc95
7
+ data.tar.gz: 2a660e68240cf6a295873eb099b635d756482419703034074fcd00fe934a7cb8ca7e26b6d8e850c8eb95e0a1cefe2d8183bde53d4a97641d3b43a416f62bb3a5
data/MIT-LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright 2016 palkan
1
+ Copyright 2016-2020 palkan
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
data/README.md CHANGED
@@ -1,4 +1,7 @@
1
- [![Gem Version](https://badge.fury.io/rb/anyt.svg)](https://rubygems.org/gems/anyt) [![Circle CI](https://circleci.com/gh/anycable/anycablebility/tree/master.svg?style=svg)](https://circleci.com/gh/anycable/anycablebility/tree/master) [![Gitter](https://img.shields.io/badge/gitter-join%20chat%20%E2%86%92-brightgreen.svg)](https://gitter.im/anycable/anycablebility)
1
+ [![Cult Of Martians](http://cultofmartians.com/assets/badges/badge.svg)](https://cultofmartians.com/tasks/anycable-conformance-tool.html#task)
2
+ [![Gem Version](https://badge.fury.io/rb/anyt.svg)](https://rubygems.org/gems/anyt)
3
+ ![Test](https://github.com/anycable/anyt/workflows/Test/badge.svg)
4
+ [![Gitter](https://img.shields.io/badge/gitter-join%20chat%20%E2%86%92-brightgreen.svg)](https://gitter.im/anycable/anycablebility)
2
5
 
3
6
  # AnyCable Conformance Testing Tool
4
7
 
data/lib/anyt/cli.rb CHANGED
@@ -4,13 +4,13 @@ require "logger"
4
4
  require "optparse"
5
5
 
6
6
  require "anyt/version"
7
+ require "anyt/remote_control"
7
8
  require "anyt/rpc"
8
9
  require "anyt/command"
9
10
  require "anyt/tests"
10
11
 
11
- # rubocop: disable Metrics/AbcSize
12
- # rubocop: disable Metrics/MethodLength
13
- # rubocop: disable Metrics/BlockLength
12
+ $stdout.sync = true
13
+
14
14
  module Anyt
15
15
  module Cli # :nodoc:
16
16
  class << self
@@ -25,21 +25,39 @@ module Anyt
25
25
  $stdout.puts "Starting AnyT v#{Anyt::VERSION} (pid: #{Process.pid})\n"
26
26
 
27
27
  begin
28
+ # "Enable" AnyCable as early as possible to activate all the features in tests
29
+ unless Anyt.config.use_action_cable
30
+ ActionCable.server.config.cable = {"adapter" => "any_cable"}
31
+ require "anycable-rails"
32
+ end
33
+
28
34
  # Load all test scenarios
29
- Tests.load_tests
35
+ Tests.load_tests unless @skip_tests
36
+
37
+ Rails.application.initialize!
30
38
 
31
39
  # Start RPC server (unless specified otherwise, e.g. when
32
40
  # we want to test Action Cable itself)
33
41
  unless @skip_rpc
34
- require "anycable-rails"
35
42
  RPC.start
43
+
44
+ if @only_rpc
45
+ RPC.server.wait_till_terminated
46
+ return
47
+ end
36
48
  end
37
49
 
38
50
  # Start webosocket server under test
39
51
  Command.run
40
52
 
41
- # Run tests
42
- result = Tests.run ? 0 : 1
53
+ unless @skip_tests
54
+ # Run tests
55
+ result = Tests.run ? 0 : 1
56
+ end
57
+
58
+ wait_till_terminated if @only_rails
59
+ rescue Interrupt => e
60
+ $stdout.puts "#{e.message}. Good-bye!"
43
61
  ensure
44
62
  RPC.stop unless @skip_rpc
45
63
  Command.stop
@@ -77,26 +95,45 @@ module Anyt
77
95
  @skip_rpc = flag
78
96
  end
79
97
 
98
+ cli.on("--only-rpc", TrueClass, "Run only RPC server") do |flag|
99
+ @only_rpc = flag
100
+ @skip_tests = true
101
+ end
102
+
103
+ cli.on("--only-rails", TrueClass, "Run only Rails server") do
104
+ @skip_rpc = true
105
+ @only_rails = true
106
+ @skip_tests = true
107
+
108
+ configure_rails_command!
109
+ end
110
+
80
111
  cli.on("--self-check", "Run tests again Action Cable itself") do
81
112
  @skip_rpc = true
82
- dummy_path = ::File.expand_path(
83
- "config.ru",
84
- ::File.join(::File.dirname(__FILE__), "dummy")
85
- )
86
- Anyt.config.command = "bundle exec puma #{dummy_path}"
113
+
114
+ configure_rails_command!
87
115
  end
88
116
 
89
117
  cli.on("--only test1,test2,test3", Array, "Run only specified tests") do |only_tests|
90
118
  Anyt.config.only_tests = only_tests
91
119
  end
92
120
 
121
+ cli.on("--except test1,test2,test3", Array, "Exclude specified tests") do |except_tests|
122
+ Anyt.config.except_tests = except_tests
123
+ end
124
+
93
125
  cli.on("--wait-command=TIMEOUT", Integer,
94
- "Number of seconds to wait for WS server initialization") do |timeout|
126
+ "Number of seconds to wait for WS server initialization") do |timeout|
95
127
  Anyt.config.wait_command = timeout
96
128
  end
97
129
 
130
+ cli.on("--timeout-multiplier=VALUE", Float,
131
+ "Default exceptation timeouts multiplier") do |val|
132
+ Anyt.config.timeout_multiplier = val
133
+ end
134
+
98
135
  cli.on("-rPATH", "--require=PATH",
99
- "Path to additional tests (e.g. features/*.rb") do |path|
136
+ "Path to additional tests (e.g. features/*.rb") do |path|
100
137
  Anyt.config.tests_relative_path = path
101
138
  ENV["ANYT_TESTS_RELATIVE_PATH"] = path
102
139
  end
@@ -123,6 +160,36 @@ module Anyt
123
160
  puts "Use `anyt --help` to list all available options."
124
161
  exit 1
125
162
  end
163
+
164
+ def configure_rails_command!
165
+ dummy_path = ::File.expand_path(
166
+ "config.ru",
167
+ ::File.join(::File.dirname(__FILE__), "dummy")
168
+ )
169
+ Anyt.config.command = "bundle exec puma #{dummy_path}"
170
+ Anyt.config.use_action_cable = true
171
+ end
172
+
173
+ def wait_till_terminated
174
+ self_read = setup_signals
175
+
176
+ while readable_io = IO.select([self_read]) # rubocop:disable Lint/AssignmentInCondition
177
+ signal = readable_io.first[0].gets.strip
178
+ raise Interrupt, "SIG#{signal} received"
179
+ end
180
+ end
181
+
182
+ def setup_signals
183
+ self_read, self_write = IO.pipe
184
+
185
+ %w[INT TERM].each do |signal|
186
+ trap signal do
187
+ self_write.puts signal
188
+ end
189
+ end
190
+
191
+ self_read
192
+ end
126
193
  end
127
194
  end
128
195
  end
data/lib/anyt/client.rb CHANGED
@@ -17,14 +17,17 @@ module Anyt
17
17
  # rubocop: disable Metrics/BlockLength
18
18
  def initialize(
19
19
  ignore: [], url: Anyt.config.target_url, qs: "",
20
- cookies: "", headers: {}
20
+ cookies: "", headers: {},
21
+ timeout_multiplier: Anyt.config.timeout_multiplier
21
22
  )
22
23
  ignore_message_types = @ignore_message_types = ignore
23
24
  messages = @messages = Queue.new
24
25
  closed = @closed = Concurrent::Event.new
25
26
  has_messages = @has_messages = Concurrent::Semaphore.new(0)
26
27
 
27
- headers = headers.merge('cookie' => cookies)
28
+ @timeout_multiplier = timeout_multiplier
29
+
30
+ headers = headers.merge("cookie" => cookies)
28
31
 
29
32
  open = Concurrent::Promise.new
30
33
 
@@ -68,13 +71,15 @@ module Anyt
68
71
  end
69
72
  end
70
73
 
71
- open.wait!(WAIT_WHEN_EXPECTING_EVENT)
74
+ open.wait!(WAIT_WHEN_EXPECTING_EVENT * @timeout_multiplier)
72
75
  end
73
76
  # rubocop: enable Metrics/BlockLength
74
77
  # rubocop: enable Metrics/AbcSize
75
78
  # rubocop: enable Metrics/MethodLength
76
79
 
77
80
  def receive(timeout: WAIT_WHEN_EXPECTING_EVENT)
81
+ timeout *= @timeout_multiplier
82
+
78
83
  raise TimeoutError, "Timed out to receive message" unless
79
84
  @has_messages.try_acquire(1, timeout)
80
85
 
@@ -88,17 +93,17 @@ module Anyt
88
93
  @ws.send(JSON.generate(message))
89
94
  end
90
95
 
91
- def close
92
- sleep WAIT_WHEN_NOT_EXPECTING_EVENT
96
+ def close(allow_messages: false)
97
+ sleep WAIT_WHEN_NOT_EXPECTING_EVENT * @timeout_multiplier
93
98
 
94
- raise "#{@messages.size} messages unprocessed" unless @messages.empty?
99
+ raise "#{@messages.size} messages unprocessed" unless allow_messages || @messages.empty?
95
100
 
96
101
  @ws.close
97
102
  wait_for_close
98
103
  end
99
104
 
100
105
  def wait_for_close
101
- @closed.wait(WAIT_WHEN_EXPECTING_EVENT)
106
+ @closed.wait(WAIT_WHEN_EXPECTING_EVENT * @timeout_multiplier)
102
107
  end
103
108
 
104
109
  def closed?
data/lib/anyt/command.rb CHANGED
@@ -1,50 +1,65 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "childprocess"
4
+
3
5
  module Anyt
4
6
  # Runs system command (websocket server)
5
7
  module Command
6
8
  class << self
7
- attr_accessor :running
8
-
9
9
  # rubocop: disable Metrics/MethodLength
10
10
  # rubocop: disable Metrics/AbcSize
11
11
  def run
12
- return if @running
12
+ return if running?
13
+
14
+ raise "Please, specify command via -c (--command) option" unless Anyt.config.command
13
15
 
14
16
  AnyCable.logger.debug "Running command: #{Anyt.config.command}"
15
17
 
16
- out = AnyCable.config.debug ? STDOUT : IO::NULL
18
+ @process = ChildProcess.build(*Anyt.config.command.split(/\s+/))
17
19
 
18
- @pid = Process.spawn(
19
- Anyt.config.command,
20
- out: out,
21
- err: out
22
- )
20
+ process.io.inherit! if AnyCable.config.debug
23
21
 
24
- Process.detach(@pid)
22
+ process.detach = true
25
23
 
26
- AnyCable.logger.debug "Command PID: #{@pid}"
24
+ process.environment["ANYCABLE_DEBUG"] = "1" if AnyCable.config.debug?
25
+ process.environment["ANYT_REMOTE_CONTROL_PORT"] = Anyt.config.remote_control_port
27
26
 
28
- @running = true
27
+ process.start
28
+
29
+ AnyCable.logger.debug "Command PID: #{process.pid}"
29
30
 
30
31
  sleep Anyt.config.wait_command
32
+ raise "Command failed to start" unless running?
31
33
  end
32
34
  # rubocop: enable Metrics/MethodLength
33
35
  # rubocop: enable Metrics/AbcSize
34
36
 
35
- def stop
36
- return unless @running
37
+ def restart
38
+ return unless running?
39
+
40
+ AnyCable.logger.debug "Restarting command PID: #{process.pid}"
37
41
 
38
- AnyCable.logger.debug "Terminate PID: #{@pid}"
42
+ stop
43
+ process.wait
39
44
 
40
- Process.kill("SIGKILL", @pid)
45
+ run
46
+ end
47
+
48
+ def stop
49
+ return unless running?
41
50
 
42
- @running = false
51
+ AnyCable.logger.debug "Terminate PID: #{process.pid}"
52
+
53
+ process.stop
43
54
  end
44
55
 
45
56
  def running?
46
- @running == true
57
+ process&.alive?
47
58
  end
59
+
60
+ private
61
+
62
+ attr_reader :process
48
63
  end
49
64
  end
50
65
  end
data/lib/anyt/config.rb CHANGED
@@ -6,10 +6,14 @@ module Anyt
6
6
  # Anyt configuration
7
7
  class Config < Anyway::Config
8
8
  attr_config :command,
9
- :only_tests,
10
- :tests_relative_path,
11
- target_url: "ws://localhost:9292/cable",
12
- wait_command: 2
9
+ :only_tests,
10
+ :except_tests,
11
+ :tests_relative_path,
12
+ remote_control_port: 8919,
13
+ use_action_cable: false,
14
+ target_url: "ws://localhost:9292/cable",
15
+ wait_command: 2,
16
+ timeout_multiplier: 1
13
17
 
14
18
  def tests_path
15
19
  return unless tests_relative_path
@@ -18,12 +22,16 @@ module Anyt
18
22
  end
19
23
 
20
24
  def filter_tests?
21
- !only_tests.nil?
25
+ only_tests || except_tests
22
26
  end
23
27
 
24
28
  def tests_filter
25
- @tests_filter ||= begin
26
- /(#{only_tests.join('|')})/
29
+ only_rxp = /(#{only_tests.join('|')})/ if only_tests
30
+ except_rxp = /(#{except_tests.join('|')})/ if except_tests
31
+
32
+ @tests_filter ||= lambda do |path|
33
+ (only_rxp.nil? || only_rxp.match?(path)) &&
34
+ (except_rxp.nil? || !except_rxp.match?(path))
27
35
  end
28
36
  end
29
37
  end
@@ -10,11 +10,11 @@ require "anycable-rails"
10
10
  require "action_dispatch/middleware/cookies"
11
11
 
12
12
  class TestApp < Rails::Application
13
- secrets.secret_token = "secret_token"
13
+ secrets.secret_token = "secret_token"
14
14
  secrets.secret_key_base = "secret_key_base"
15
15
 
16
16
  config.logger = Logger.new(STDOUT)
17
- config.log_level = :fatal
17
+ config.log_level = AnyCable.config.log_level
18
18
  config.eager_load = true
19
19
 
20
20
  config.paths["config/routes.rb"] << File.join(__dir__, "routes.rb")
@@ -24,13 +24,15 @@ module ApplicationCable
24
24
  class Connection < ActionCable::Connection::Base
25
25
  delegate :params, to: :request
26
26
 
27
+ identified_by :uid
28
+
27
29
  def connect
28
- logger.info "Connected"
30
+ logger.debug "Connected"
29
31
  Anyt::ConnectHandlers.call(self)
30
32
  end
31
33
 
32
34
  def disconnect
33
- logger.info "Disconnected"
35
+ logger.debug "Disconnected"
34
36
  end
35
37
  end
36
38
  end
@@ -40,8 +42,31 @@ module ApplicationCable
40
42
  end
41
43
  end
42
44
 
43
- ActionCable.server.config.cable = { "adapter" => "redis" }
45
+ # BenchmarkChannel is useful when running Rails app only or RPC only
46
+ class BenchmarkChannel < ApplicationCable::Channel
47
+ def subscribed
48
+ stream_from "all#{stream_id}"
49
+ end
50
+
51
+ def echo(data)
52
+ transmit data
53
+ end
54
+
55
+ def broadcast(data)
56
+ ActionCable.server.broadcast "all#{stream_id}", data
57
+ data["action"] = "broadcastResult"
58
+ transmit data
59
+ end
60
+
61
+ private
62
+
63
+ def stream_id
64
+ params[:id] || ""
65
+ end
66
+ end
67
+
68
+ ActionCable.server.config.cable = {"adapter" => "redis"}
44
69
  ActionCable.server.config.connection_class = -> { ApplicationCable::Connection }
45
70
  ActionCable.server.config.disable_request_forgery_protection = true
46
71
  ActionCable.server.config.logger =
47
- Rails.logger = Logger.new(STDOUT).tap { |l| l.level = Logger::DEBUG }
72
+ Rails.logger
@@ -3,6 +3,10 @@
3
3
  require_relative "./application"
4
4
 
5
5
  require_relative "../tests"
6
+ require_relative "../remote_control"
7
+
8
+ # Start remote control
9
+ Anyt::RemoteControl::Server.start(Anyt.config.remote_control_port)
6
10
 
7
11
  # Load channels from tests
8
12
  Anyt::Tests.load_all_tests
@@ -0,0 +1 @@
1
+ fe2ecbcf229d5547a64bcdaa9ef4e543404ca3fc9d4ee83aaa7ddab34b6a378fe090c2b1836c9ebd3e5ebbf01c239ddac95e219358baaefc1afaa04cd07bf78c
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ ENV["TERM"] = "#{ENV["TERM"]}color" unless ENV["TERM"]&.match?(/color/)
3
4
  require "minitest/spec"
4
5
  require "minitest/reporters"
5
6
 
@@ -8,10 +9,26 @@ module Anyt
8
9
  module TestHelpers
9
10
  def self.included(base)
10
11
  base.let(:client) { build_client(ignore: %w[ping welcome]) }
12
+ base.after { @clients&.each { |client| client.close(allow_messages: true) } }
11
13
  end
12
14
 
13
- def build_client(*args)
14
- Anyt::Client.new(*args)
15
+ def build_client(**args)
16
+ @clients ||= []
17
+ Anyt::Client.new(**args).tap do |client|
18
+ @clients << client
19
+ end
20
+ end
21
+
22
+ def restart_server!
23
+ if Anyt.config.use_action_cable
24
+ remote_client.restart_action_cable
25
+ else
26
+ Command.restart
27
+ end
28
+ end
29
+
30
+ def remote_client
31
+ @remote_client ||= RemoteControl::Client.connect(Anyt.config.remote_control_port)
15
32
  end
16
33
  end
17
34
  end
@@ -38,7 +55,7 @@ module Anyt
38
55
 
39
56
  def handlers_for(connection)
40
57
  handlers.select do |(tag, _)|
41
- connection.params['test'] == tag
58
+ connection.params["test"] == tag
42
59
  end
43
60
  end
44
61
 
@@ -74,7 +91,7 @@ module Minitest::Spec::DSL
74
91
  # Generates Channel class dynamically and
75
92
  # add memoized helper to access its name
76
93
  def channel(id = nil, &block)
77
- class_name = @name.gsub(/\s+/, '_')
94
+ class_name = @name.gsub(/\s+/, "_")
78
95
  class_name += "_#{id}" if id
79
96
  class_name += "_channel"
80
97
 
@@ -99,15 +116,15 @@ module Anyt
99
116
  module MinitestPatch
100
117
  def load_plugins
101
118
  super
102
- extensions.delete('rails')
119
+ extensions.delete("rails")
103
120
  end
104
121
  end
105
122
 
106
123
  # Patch Spec reporter
107
124
  module ReporterPatch # :nodoc:
108
125
  def record_print_status(test)
109
- test_name = test.name.gsub(/^test_/, '').strip
110
- print pad_test(test_name)
126
+ test_name = test.name.gsub(/^test_/, "").strip
127
+ print(magenta { pad_test(test_name) })
111
128
  print_colored_status(test)
112
129
  print(" (%.2fs)" % test.time) unless test.time.nil?
113
130
  puts