diff --git a/.gitignore b/.gitignore index 0cb6eeb..9f0c417 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,5 @@ /pkg/ /spec/reports/ /tmp/ +/.idea/ +/spec/examples.txt diff --git a/.idea/message_bus-client.iml b/.idea/message_bus-client.iml deleted file mode 100644 index af03825..0000000 --- a/.idea/message_bus-client.iml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/.rubocop.unhound.yml b/.rubocop.unhound.yml index 19528da..67e7801 100644 --- a/.rubocop.unhound.yml +++ b/.rubocop.unhound.yml @@ -1,44 +1,41 @@ -AccessorMethodName: +Style/AccessorMethodName: Enabled: true -ActionFilter: +Style/Alias: Enabled: true -Alias: +Style/ArrayJoin: Enabled: true -ArrayJoin: +Style/AsciiComments: Enabled: true -AsciiComments: +Style/AsciiIdentifiers: Enabled: true -AsciiIdentifiers: +Style/Attr: Enabled: true -Attr: +Metrics/BlockNesting: Enabled: true -BlockNesting: +Style/CaseEquality: Enabled: true -CaseEquality: +Style/CharacterLiteral: Enabled: true -CharacterLiteral: - Enabled: true - -ClassAndModuleChildren: +Style/ClassAndModuleChildren: Enabled: true EnforcedStyle: compact -ClassLength: +Metrics/ClassLength: Enabled: true -ClassVars: +Style/ClassVars: Enabled: true -CollectionMethods: +Style/CollectionMethods: Enabled: true PreferredMethods: reduce: @@ -46,199 +43,193 @@ CollectionMethods: find: detect: 'find' -ColonMethodCall: - Enabled: true - -CommentAnnotation: +Style/ColonMethodCall: Enabled: true -CyclomaticComplexity: +Style/CommentAnnotation: Enabled: true -Delegate: +Metrics/CyclomaticComplexity: Enabled: true -DeprecatedHashMethods: +Style/PreferredHashMethods: Enabled: true -DoubleNegation: +Style/DoubleNegation: Enabled: true -EachWithObject: +Style/EachWithObject: Enabled: true -EmptyLiteral: +Style/EmptyLiteral: Enabled: true -Encoding: +Style/Encoding: Enabled: false -EvenOdd: +Style/EvenOdd: Enabled: true -ExtraSpacing: +Layout/ExtraSpacing: Enabled: false -FileName: +Style/FileName: Enabled: true -FlipFlop: +Style/FlipFlop: Enabled: true -FormatString: +Style/FormatString: Enabled: true -GlobalVars: +Style/GlobalVars: Enabled: true -GuardClause: +Style/GuardClause: Enabled: true -IfUnlessModifier: +Style/IfUnlessModifier: Enabled: true -IfWithSemicolon: +Style/IfWithSemicolon: Enabled: true -InlineComment: +Style/InlineComment: Enabled: false -Lambda: +Style/Lambda: Enabled: true -LambdaCall: +Style/LambdaCall: Enabled: true -LineEndConcatenation: +Style/LineEndConcatenation: Enabled: true -MethodLength: +Metrics/MethodLength: Enabled: true -ModuleFunction: +Style/ModuleFunction: Enabled: true -NegatedIf: +Style/NegatedIf: Enabled: true -NegatedWhile: +Style/NegatedWhile: Enabled: true -Next: +Style/Next: Enabled: true -NilComparison: +Style/NilComparison: Enabled: true -Not: +Style/Not: Enabled: true -NumericLiterals: +Style/NumericLiterals: Enabled: true -OneLineConditional: +Style/OneLineConditional: Enabled: true -OpMethod: +Style/OpMethod: Enabled: true -ParameterLists: +Metrics/ParameterLists: Enabled: true -PercentLiteralDelimiters: +Style/PercentLiteralDelimiters: Enabled: true -PerlBackrefs: +Style/PerlBackrefs: Enabled: true -PredicateName: +Style/PredicateName: NamePrefixBlacklist: [] -Proc: +Style/Proc: Enabled: true -RaiseArgs: +Style/RaiseArgs: Enabled: true -RegexpLiteral: +Style/RegexpLiteral: Enabled: true -SelfAssignment: +Style/SelfAssignment: Enabled: true -SingleLineBlockParams: +Style/SingleLineBlockParams: Enabled: true -SingleLineMethods: +Style/SingleLineMethods: Enabled: true -SignalException: +Style/SignalException: Enabled: true -SpecialGlobalVars: +Style/SpecialGlobalVars: Enabled: true -VariableInterpolation: +Style/VariableInterpolation: Enabled: true -TrailingComma: +Style/TrailingCommaInLiteral: Enabled: true -TrivialAccessors: +Style/TrivialAccessors: Enabled: true -VariableInterpolation: +Style/VariableInterpolation: Enabled: true -WhenThen: +Style/WhenThen: Enabled: true -WhileUntilModifier: +Style/WhileUntilModifier: Enabled: true # Lint -AmbiguousOperator: - Enabled: true - -AmbiguousRegexpLiteral: +Lint/AmbiguousOperator: Enabled: true -AssignmentInCondition: +Lint/AmbiguousRegexpLiteral: Enabled: true -ConditionPosition: +Lint/AssignmentInCondition: Enabled: true -DeprecatedClassMethods: +Lint/ConditionPosition: Enabled: true -ElseLayout: +Lint/ElseLayout: Enabled: true -HandleExceptions: +Lint/HandleExceptions: Enabled: true -InvalidCharacterLiteral: +Lint/InvalidCharacterLiteral: Enabled: true -LiteralInCondition: +Lint/LiteralInCondition: Enabled: true -LiteralInInterpolation: +Lint/LiteralInInterpolation: Enabled: true -Loop: +Lint/Loop: Enabled: true -ParenthesesAsGroupedExpression: +Lint/ParenthesesAsGroupedExpression: Enabled: true -RequireParentheses: +Lint/RequireParentheses: Enabled: true -UnderscorePrefixedVariableName: +Lint/UnderscorePrefixedVariableName: Enabled: true -Void: +Lint/Void: Enabled: true diff --git a/.rubocop.yml b/.rubocop.yml index 7418e3c..7e19276 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,16 +1,19 @@ inherit_from: - .rubocop.unhound.yml +AllCops: + TargetRubyVersion: 2.3 + Metrics/LineLength: Max: 100 -Style/DotPosition: +Layout/DotPosition: EnforcedStyle: trailing Style/Documentation: Enabled: false -Style/IndentHash: +Layout/IndentHash: EnforcedStyle: consistent Style/StringLiterals: @@ -24,3 +27,14 @@ Style/RegexpLiteral: Style/ClassAndModuleChildren: EnforcedStyle: compact + +Metrics/BlockLength: + Exclude: + - 'Rakefile' + - '**/*.rake' + - 'spec/**/*_spec.rb' + +Style/FrozenStringLiteralComment: + Enabled: true + Exclude: + - 'spec/**/*.rb' diff --git a/.travis.yml b/.travis.yml index bb45ab6..4dd93db 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,5 @@ language: ruby rvm: - - 2.1.9 - - 2.2.5 - 2.3.1 + - 2.4.1 before_install: gem update bundler diff --git a/Gemfile b/Gemfile index a7f86bd..eae9336 100644 --- a/Gemfile +++ b/Gemfile @@ -1,4 +1,4 @@ source 'https://rubygems.org' -# Specify your gem's dependencies in message_bus-client.gemspec +# Specify your gem's dependencies in message_bus_client.gemspec gemspec diff --git a/README.md b/README.md index 02f36d3..d74e52e 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ -# MessageBus::Client -[![Build Status](https://travis-ci.org/lowjoel/message_bus-client.svg?branch=master)](https://travis-ci.org/lowjoel/message_bus-client)[![Coverage Status](https://coveralls.io/repos/github/lowjoel/message_bus-client/badge.svg?branch=master)](https://coveralls.io/github/lowjoel/message_bus-client?branch=master)[![Code Climate](https://codeclimate.com/github/lowjoel/message_bus-client/badges/gpa.svg)](https://codeclimate.com/github/lowjoel/message_bus-client)[![security](https://hakiri.io/github/lowjoel/message_bus-client/master.svg)](https://hakiri.io/github/lowjoel/message_bus-client/master)[![Inline docs](http://inch-ci.org/github/lowjoel/message_bus-client.svg?branch=master)](http://inch-ci.org/github/lowjoel/message_bus-client) +# MessageBusClient +[![Build Status](https://travis-ci.org/lowjoel/message_bus_client.svg?branch=master)](https://travis-ci.org/lowjoel/message_bus_client)[![Coverage Status](https://coveralls.io/repos/github/lowjoel/message_bus_client/badge.svg?branch=master)](https://coveralls.io/github/lowjoel/message_bus_client?branch=master)[![Code Climate](https://codeclimate.com/github/lowjoel/message_bus_client/badges/gpa.svg)](https://codeclimate.com/github/lowjoel/message_bus_client)[![security](https://hakiri.io/github/lowjoel/message_bus_client/master.svg)](https://hakiri.io/github/lowjoel/message_bus_client/master)[![Inline docs](http://inch-ci.org/github/lowjoel/message_bus_client.svg?branch=master)](http://inch-ci.org/github/lowjoel/message_bus_client) This is a Ruby implementation of the client for [message_bus](https://github.com/samsaffron/message_bus). @@ -9,7 +9,7 @@ This is a Ruby implementation of the client for Add this line to your application's Gemfile: ```ruby -gem 'message_bus-client' +gem 'message_bus_client' ``` And then execute: @@ -18,14 +18,14 @@ And then execute: Or install it yourself as: - $ gem install message_bus-client + $ gem install message_bus_client ## Usage The API is mostly equivalent with the JavaScript client: ```ruby -client = MessageBus::Client.new('http://chat.samsaffron.com/') +client = MessageBusClient.new('http://chat.samsaffron.com/') client.subscribe('/message') do |payload| # Do stuff end @@ -39,8 +39,8 @@ client.stop Both Long Polling and normal polling are supported: ```ruby -MessageBus::Client.long_polling = true # false to disable -MessageBus::Client.poll_interval = 15 # seconds +MessageBusClient.long_polling = true # false to disable +MessageBusClient.poll_interval = 15 # seconds ``` ## Development @@ -54,7 +54,7 @@ If you are running Windows, Ruby is not able to kill the server process. Run it ## Contributing Bug reports and pull requests are welcome on GitHub at -https://github.com/lowjoel/message_bus-client. +https://github.com/lowjoel/message_bus_client. ## MIT License diff --git a/Rakefile b/Rakefile index 7c6aacf..4c774a2 100644 --- a/Rakefile +++ b/Rakefile @@ -3,22 +3,4 @@ require 'rspec/core/rake_task' RSpec::Core::RakeTask.new(:spec) -task :server do - pid = 0 - Bundler.with_clean_env do - nul = RbConfig::CONFIG['host_os'] =~ /mswin|mingw/ ? 'nul' : '/dev/null' - pid = spawn "bundle exec puma 'spec/chat_server.ru' 2>#{nul}" - end - - at_exit do - $stderr.puts "Killing pid #{pid}" - Process.kill('KILL', pid) - Process.wait(pid) - end - - sleep 3 -end - -task :spec => :server - task default: :spec diff --git a/lib/message_bus/client.rb b/lib/message_bus/client.rb deleted file mode 100644 index 5bb8184..0000000 --- a/lib/message_bus/client.rb +++ /dev/null @@ -1,18 +0,0 @@ -require 'excon' -require 'json' - -require 'message_bus/client/version' -require 'message_bus/client/configuration' -require 'message_bus/client/connection' -require 'message_bus/client/message_handler' - -class MessageBus::Client - include MessageBus::Client::Configuration - include MessageBus::Client::Connection - include MessageBus::Client::MessageHandler - - def initialize(base_url) - super - @client_id = SecureRandom.uuid - end -end diff --git a/lib/message_bus/client/version.rb b/lib/message_bus/client/version.rb deleted file mode 100644 index 10281ec..0000000 --- a/lib/message_bus/client/version.rb +++ /dev/null @@ -1,5 +0,0 @@ -module MessageBus - class Client - VERSION = '0.1.0' - end -end diff --git a/lib/message_bus_client.rb b/lib/message_bus_client.rb new file mode 100644 index 0000000..d15d4c2 --- /dev/null +++ b/lib/message_bus_client.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +require 'excon' +require 'json' +require 'securerandom' + +require 'message_bus_client/version' +require 'message_bus_client/configuration' +require 'message_bus_client/connection' +require 'message_bus_client/message_handler' + +class MessageBusClient + include MessageBusClient::Configuration + include MessageBusClient::Connection + include MessageBusClient::MessageHandler + + def initialize(base_url) + super + @client_id = SecureRandom.uuid + end +end diff --git a/lib/message_bus/client/configuration.rb b/lib/message_bus_client/configuration.rb similarity index 79% rename from lib/message_bus/client/configuration.rb rename to lib/message_bus_client/configuration.rb index eea479f..aa9e368 100644 --- a/lib/message_bus/client/configuration.rb +++ b/lib/message_bus_client/configuration.rb @@ -1,4 +1,6 @@ -module MessageBus::Client::Configuration +# frozen_string_literal: true + +module MessageBusClient::Configuration def self.included(module_) module_.extend(ClassMethods) end diff --git a/lib/message_bus/client/connection.rb b/lib/message_bus_client/connection.rb similarity index 63% rename from lib/message_bus/client/connection.rb rename to lib/message_bus_client/connection.rb index 59c506b..4c1dc2b 100644 --- a/lib/message_bus/client/connection.rb +++ b/lib/message_bus_client/connection.rb @@ -1,18 +1,20 @@ -module MessageBus::Client::Connection +# frozen_string_literal: true + +module MessageBusClient::Connection # The connection is in the initialised state. - INITIALISED = 0 + INITIALISED = :initialised # The connection is in the started state. - STARTED = 1 + STARTED = :started # The connection is in the paused state. - PAUSED = 2 + PAUSED = :paused # The connection is in the stopping state. - STOPPING = 3 + STOPPING = :stopping # The connection is in the stopped state. - STOPPED = 4 + STOPPED = :stopped def initialize(base_url) @connection = nil @@ -23,16 +25,18 @@ def initialize(base_url) @statistics = { total_calls: 0, failed_calls: 0 } end - def diagnostics - end + def diagnostics; end - def start + def start(**options) return unless @state == INITIALISED || stopped? - @state = STARTED - @connection = Excon.new(server_endpoint, persistent: true) - @runner = Thread.new { runner } + @connection = Excon.new(server_endpoint, persistent: true, **options) + + @runner = Thread.new(&method(:runner)) + @runner.name = "MessageBusClient (#{@client_id})" @runner.abort_on_exception = true + + @state = STARTED end def pause @@ -52,12 +56,22 @@ def resume handle_messages end - def stop - return unless @state == STARTED || @state == PAUSED + def stop(timeout = nil) + fail ThreadError if Thread.current == @runner + + return if should_stop? || stopped? @state = STOPPING @connection.reset - @runner.join + + wakeup # break out of light sleep when polling + + unless @runner.join(timeout) + @runner.kill + @runner.join # just killing the thread is not enough to finish the work + end + + @runner.stop? end def stopped? @@ -66,9 +80,13 @@ def stopped? private + def should_stop? + @state == STOPPING + end + # The runner handling polling over the connection. def runner - poll until @state == STOPPING + poll until should_stop? rescue Excon::Errors::Error @statistics[:failed_calls] += 1 retry @@ -106,15 +124,25 @@ def headers # Gets the URI to poll the server with def server_endpoint - endpoint = "#{@base_url}/message-bus/#{@client_id}/poll" - endpoint << "?dlp=t" unless self.class.long_polling + endpoint = +"#{@base_url}/message-bus/#{@client_id}/poll" + endpoint << '?dlp=t' unless self.class.long_polling + + endpoint.freeze + end + + def light_sleep(seconds) + return if should_stop? + @_sleep_check, @_sleep_interrupt = IO.pipe + IO.select([@_sleep_check], nil, nil, seconds) + end - endpoint + def wakeup + @_sleep_interrupt.close if defined?(@_sleep_interrupt) && !@_sleep_interrupt.closed? end # Handles the response from the connection. def handle_connection_response(response) handle_response(response.body) - sleep(self.class.poll_interval) + light_sleep(self.class.poll_interval) end end diff --git a/lib/message_bus/client/message_handler.rb b/lib/message_bus_client/message_handler.rb similarity index 92% rename from lib/message_bus/client/message_handler.rb rename to lib/message_bus_client/message_handler.rb index 71bb0bc..6902ef6 100644 --- a/lib/message_bus/client/message_handler.rb +++ b/lib/message_bus_client/message_handler.rb @@ -1,4 +1,6 @@ -module MessageBus::Client::MessageHandler +# frozen_string_literal: true + +module MessageBusClient::MessageHandler SubscribedChannel = Struct.new(:callbacks, :last_id) do def initialize(last_id = -1) self.callbacks = [] @@ -23,15 +25,14 @@ def initialize(base_url) @subscribed_channels.default_proc = proc do |hash, key| hash[key] = SubscribedChannel.new end - @payload = String.new + @payload = +'' end def subscribe(channel, &callback) @subscribed_channels[channel].callbacks << callback end - def unsubscribe - end + def unsubscribe; end private @@ -50,7 +51,7 @@ def handle_chunk(chunk, _remaining_bytes, _total_bytes) end def handle_response(body) - handle_messages(JSON.parse(body)) + handle_messages(JSON.parse(body)) unless body.empty? end def try_consume_message @@ -71,7 +72,6 @@ def handle_messages(messages = nil) @pending_messages.each(&handle_message_method) messages.each(&handle_message_method) if messages end - end def handle_message(message) diff --git a/lib/message_bus_client/version.rb b/lib/message_bus_client/version.rb new file mode 100644 index 0000000..54ac497 --- /dev/null +++ b/lib/message_bus_client/version.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +class MessageBusClient + VERSION = '0.2.0' +end diff --git a/message_bus-client.gemspec b/message_bus_client.gemspec similarity index 83% rename from message_bus-client.gemspec rename to message_bus_client.gemspec index 9f4d4e8..32c2a9d 100644 --- a/message_bus-client.gemspec +++ b/message_bus_client.gemspec @@ -1,17 +1,18 @@ # coding: utf-8 + lib = File.expand_path('../lib', __FILE__) $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) -require 'message_bus/client/version' +require 'message_bus_client/version' Gem::Specification.new do |spec| - spec.name = 'message_bus-client' - spec.version = MessageBus::Client::VERSION + spec.name = 'message_bus_client' + spec.version = MessageBusClient::VERSION spec.authors = ['Joel Low'] spec.email = ['joel@joelsplace.sg'] spec.summary = 'Ruby client for Message Bus' spec.description = 'Implements a client for Message Bus, with communication over HTTP' - spec.homepage = 'https://github.com/lowjoel/message_bus-client' + spec.homepage = 'https://github.com/lowjoel/message_bus_client' spec.license = 'MIT' spec.files = `git ls-files -z`.split("\x0"). @@ -33,4 +34,6 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'sinatra' spec.add_dependency 'excon', '~> 0.45' + + spec.required_ruby_version = '>= 2.3' end diff --git a/spec/chat_server.ru b/spec/chat_server.ru deleted file mode 100644 index b47e64d..0000000 --- a/spec/chat_server.ru +++ /dev/null @@ -1,14 +0,0 @@ -message_bus_path = Bundler.rubygems.find_name('message_bus').first.full_gem_path -chat_example_path = File.join(message_bus_path, 'examples/chat') - -# Override the load path so that the message_bus library loads before us. The middleware uses a -# MessageBus::Client class as well, with a different use case from ours. -$LOAD_PATH.unshift File.expand_path('lib', message_bus_path) - -# Use the in-memory backend since this is for testing. -require 'message_bus' -MessageBus.config[:backend] = :memory - -# Run the Chat Sinatra app. -require "#{chat_example_path}/chat" -run Chat diff --git a/spec/chat_server_helper.rb b/spec/chat_server_helper.rb new file mode 100644 index 0000000..217cee6 --- /dev/null +++ b/spec/chat_server_helper.rb @@ -0,0 +1,36 @@ +message_bus_path = Bundler.rubygems.find_name('message_bus').first.full_gem_path +chat_example_path = File.join(message_bus_path, 'examples/chat') + +# Override the load path so that the message_bus library loads before us. The middleware uses a +# MessageBusClient class as well, with a different use case from ours. +$LOAD_PATH.unshift File.expand_path('lib', message_bus_path) + +# Use the in-memory backend since this is for testing. +require 'message_bus' +MessageBus.config[:backend] = :memory + +# Run the Chat Sinatra app. +require File.join(chat_example_path, 'chat') + +RSpec.configure do |config| + config.before(:suite) do + Thread.abort_on_exception = true + Chat.set :port, 9292 + + Thread.new { Chat.run! } + + retries = 0 + begin + Socket.tcp('localhost', 9292, connect_timeout: 1) + rescue Errno::ECONNREFUSED + if (retries = retries.succ) < 10 + sleep 1 + retry + end + end + end + + config.after(:suite) do + Chat.quit! + end +end diff --git a/spec/coverage_helper.rb b/spec/coverage_helper.rb index 27fee81..cba371b 100644 --- a/spec/coverage_helper.rb +++ b/spec/coverage_helper.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Simultaneous code coverage reporting to Coveralls and Code Climate. # Latest version can be found at https://gist.github.com/lowjoel/6c2f2d3a08bb3786994f require 'simplecov' @@ -8,19 +10,16 @@ class << self # gem. # # @param [String] name The name of the module to require. - # @param [Proc] initializer The block to execute when the module is required successfully. - def load(name, &initializer) + # @yield The block to execute when the module is required successfully. + def load(name) old_formatter = SimpleCov.formatter require name - initializer.call + yield merge_formatters(old_formatter, SimpleCov.formatter) rescue LoadError => e - if e.path == name - puts format('Cannot find \'%s\', ignoring', name) if ENV['CI'] - else - raise e - end + raise e unless e.path == name + puts format('Cannot find \'%s\', ignoring', name) if ENV['CI'] end private @@ -52,14 +51,14 @@ def expand_formatter(formatter) Coveralls.wear!('rails') end - # Code Climate - CoverageHelper.load('codeclimate-test-reporter') do - CodeClimate::TestReporter.start - end - # Code coverage exclusions SimpleCov.start do # SimpleCov configuration - # add_filter '/lib/extensions/active_record/connection_adapters/table_definition.rb' + # Helpers for schema migrations. We don't test schema migrations, so these would never run. + # add_filter '/lib/extensions/legacy/active_record/connection_adapters/table_definition.rb' + + # Extra statistics to be placed in `rake stats`. We don't run that on CI, so coverage is not + # important. + # add_filter '/lib/tasks/coursemology/stats_setup.rake' end end diff --git a/spec/message_bus/client_spec.rb b/spec/message_bus_client_spec.rb similarity index 62% rename from spec/message_bus/client_spec.rb rename to spec/message_bus_client_spec.rb index 26511de..462b0f6 100644 --- a/spec/message_bus/client_spec.rb +++ b/spec/message_bus_client_spec.rb @@ -1,17 +1,17 @@ -RSpec.describe MessageBus::Client do +RSpec.describe MessageBusClient do self::SERVER_BASE = 'http://127.0.0.1:9292'.freeze it 'has a version number' do - expect(MessageBus::Client::VERSION).not_to be nil + expect(MessageBusClient::VERSION).not_to be nil end - def write_message(message, user = 'message_bus-client') + def write_message(message, user = 'message_bus_client') Excon.post(URI.join(self.class::SERVER_BASE, '/message').to_s, body: URI.encode_www_form(name: user, data: message), headers: { 'Content-Type' => 'application/x-www-form-urlencoded' }) end - subject { MessageBus::Client.new(self.class::SERVER_BASE) } + subject { MessageBusClient.new(self.class::SERVER_BASE) } context 'when using long polling' do it 'connects to the server' do @@ -19,6 +19,20 @@ def write_message(message, user = 'message_bus-client') subject.stop end + it 'can stop client quickly' do + allow(MessageBusClient).to receive(:poll_interval).and_return(2) + timeout = MessageBusClient.poll_interval.to_f / 2 + + subject.start + sleep(timeout / 2) # let the background thread start the runner + + Timeout.timeout(timeout * 1.1) do + subject.stop(timeout) + end + + expect(subject).to be_stopped + end + context 'when the connection times out' do it 'continues to long poll' do count = 0 @@ -56,14 +70,14 @@ def write_message(message, user = 'message_bus-client') context 'when using polling' do around(:each) do |example| begin - old_long_polling = MessageBus::Client.long_polling - old_poll_interval = MessageBus::Client.poll_interval - MessageBus::Client.poll_interval = 1 - MessageBus::Client.long_polling = false + old_long_polling = MessageBusClient.long_polling + old_poll_interval = MessageBusClient.poll_interval + MessageBusClient.poll_interval = 1 + MessageBusClient.long_polling = false example.call ensure - MessageBus::Client.poll_interval = old_poll_interval - MessageBus::Client.long_polling = old_long_polling + MessageBusClient.poll_interval = old_poll_interval + MessageBusClient.long_polling = old_long_polling end end @@ -72,6 +86,19 @@ def write_message(message, user = 'message_bus-client') subject.stop end + it 'can stop client quickly before the background thread starts' do + timeout = MessageBusClient.poll_interval.to_f / 2 + + subject.start + sleep(timeout / 2) # let the background thread start the runner + + Timeout.timeout(timeout * 1.1) do + subject.stop(timeout) + end + + expect(subject).to be_stopped + end + it 'receives messages' do subject.start @@ -86,6 +113,16 @@ def write_message(message, user = 'message_bus-client') sleep(1) end end + + it 'handles empty message' do + expect(subject).to receive(:handle_response).with('').and_call_original.at_least(:once) + + Excon.stub({}, body: '', status: 200) + + subject.start(mock: true, persistent: false) + + sleep(0.1) + end end it 'allows pausing messages' do @@ -107,7 +144,7 @@ def write_message(message, user = 'message_bus-client') result = false subject.subscribe('/message') do |payload| - result = result || payload['data'] == text + result ||= payload['data'] == text end subject.resume diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 81db7d9..c99ccf4 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,6 +1,7 @@ $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__) require 'coverage_helper' -require 'message_bus/client' +require 'message_bus_client' +require 'chat_server_helper' # This file was generated by the `rspec --init` command. Conventionally, all # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. @@ -54,7 +55,7 @@ # Allows RSpec to persist some state between runs in order to support # the `--only-failures` and `--next-failure` CLI options. We recommend # you configure your source control system to ignore this file. - config.example_status_persistence_file_path = "spec/examples.txt" + config.example_status_persistence_file_path = 'spec/examples.txt' # Limits the available syntax to the non-monkey patched syntax that is # recommended. For more details, see: @@ -93,4 +94,17 @@ # test failures related to randomization by passing the same `--seed` value # as the one that triggered the failure. Kernel.srand config.seed + + config.after(:each) do + Thread.list.each do |thread| + if thread.name&.match('MessageBusClient') + thread.kill + thread.join + end + end + end + + config.after(:each) do + Excon.stubs.clear + end end