anyt 0.8.5 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
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