From 83977b36cf28642ff7e10b605ce9b9d74f574c2b Mon Sep 17 00:00:00 2001 From: Michael Nikitochkin Date: Mon, 23 May 2022 16:19:23 +0200 Subject: [PATCH] Make code compatible with Shopify ruby style. Add .editorconfig to support common config for editors. Update Github action workflow to run Linter task. Update code to support Shopify ruby style. Sidecar: Update tests that never worked and always successed. --- .editorconfig | 24 ++ .gitattributes | 1 + .github/workflows/test.yml | 75 +++--- .rubocop.yml | 20 +- .rubocop_todo.yml | 15 ++ .vscode/extensions.json | 7 +- Gemfile | 44 ++-- Gemfile.lock | 9 + Rakefile | 44 ++-- ext/semian/extconf.rb | 40 ++-- lib/semian.rb | 100 ++++---- lib/semian/adapter.rb | 12 +- lib/semian/circuit_breaker.rb | 26 +- lib/semian/grpc.rb | 18 +- lib/semian/instrumentable.rb | 2 + lib/semian/lru_hash.rb | 29 +-- lib/semian/mysql2.rb | 22 +- lib/semian/net_http.rb | 14 +- lib/semian/platform.rb | 4 +- lib/semian/protected_resource.rb | 8 +- lib/semian/rails.rb | 18 +- lib/semian/redis.rb | 28 ++- lib/semian/redis_client.rb | 8 +- lib/semian/resource.rb | 8 +- lib/semian/simple_integer.rb | 6 +- lib/semian/simple_sliding_window.rb | 8 +- lib/semian/simple_state.rb | 4 +- lib/semian/unprotected_resource.rb | 2 + lib/semian/version.rb | 4 +- semian.gemspec | 26 +- test/adapter_test.rb | 18 +- test/benchmark/lru_benchmarker.rb | 32 +-- test/circuit_breaker_test.rb | 79 ++++--- test/config/semian_config.rb | 6 +- test/echo_pb.rb | 4 +- test/echo_service.rb | 6 +- test/echo_services_pb.rb | 8 +- test/grpc_test.rb | 67 +++--- test/helpers/adapter_helper.rb | 6 +- test/helpers/background_helper.rb | 2 + test/helpers/circuit_breaker_helper.rb | 6 +- test/helpers/mock_server.rb | 6 +- test/helpers/resource_helper.rb | 11 +- test/instrumentation_test.rb | 14 +- test/lru_hash_test.rb | 174 +++++++------- test/mysql2_test.rb | 180 +++++++------- test/net_http_test.rb | 186 ++++++++------- test/protected_resource_test.rb | 36 +-- test/redis_client_test.rb | 188 +++++++-------- test/redis_test.rb | 223 +++++++++--------- test/resource_test.rb | 314 ++++++++++++------------- test/semian_test.rb | 54 +++-- test/simple_integer_test.rb | 6 +- test/simple_sliding_window_test.rb | 8 +- test/simple_state_test.rb | 22 +- test/test_helper.rb | 86 +++---- test/unprotected_resource_test.rb | 22 +- 57 files changed, 1312 insertions(+), 1078 deletions(-) create mode 100644 .editorconfig create mode 100644 .gitattributes create mode 100644 .rubocop_todo.yml diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000..985493f06 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,24 @@ +# EditorConfig is awesome: https://EditorConfig.org + +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_size = 2 +indent_style = space +insert_final_newline = true +max_line_length = 120 +tab_width = 2 +trim_trailing_whitespace = true + +[{*.rb,*.rake}] +indent_size = 2 +indent_style = space + +[{*.har,*.json}] +indent_size = 2 +indent_style = space + +[{*.lock,*.yaml,*.yml}] +indent_size = 2 diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..d46506784 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +Gemfile.lock linguist-generated diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 2085ae71b..01b12308d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -11,44 +11,14 @@ concurrency: cancel-in-progress: true env: + BUNDLE_PATH: vendor/bundle SEED: 58485 jobs: - bundle: - name: Install dependency - runs-on: ubuntu-latest - # https://docs.github.com/en/actions/using-jobs/running-jobs-in-a-container - container: ruby:${{ matrix.ruby }} - - strategy: - fail-fast: false - matrix: - ruby: - # - "3.1" # grpc is causing issues with Ruby 3.1 - - "3.0" - - "2.7" - steps: - - - name: Checkout - uses: actions/checkout@v2 - - - name: Cache - uses: actions/cache@v2 - with: - path: vendor/bundle - key: ${{ runner.os }}-ruby-${{ matrix.ruby }}-gems-${{ hashFiles('**/Gemfile.lock') }} - restore-keys: | - ${{ runner.os }}-ruby-${{ matrix.ruby }}-gems- - - - name: Bundle - if: steps.cache-primes.outputs.cache-hit != 'true' - run: | - bundle config path vendor/bundle - bundle install test: name: Ruby tests runs-on: ubuntu-latest - needs: bundle + # https://docs.github.com/en/actions/using-jobs/running-jobs-in-a-container container: image: ruby:${{ matrix.ruby }} ports: @@ -61,8 +31,8 @@ jobs: env: CI: "1" + strategy: - fail-fast: true matrix: ruby: # - "3.1" # grpc is causing issues with Ruby 3.1 @@ -87,6 +57,7 @@ jobs: --health-retries 5 toxiproxy: image: ghcr.io/shopify/toxiproxy:2.4.0 + steps: - name: Checkout @@ -95,16 +66,15 @@ jobs: name: Cache uses: actions/cache@v2 with: - path: vendor/bundle + path: ${{env.BUNDLE_PATH}} key: ${{ runner.os }}-ruby-${{ matrix.ruby }}-gems-${{ hashFiles('**/Gemfile.lock') }} restore-keys: | ${{ runner.os }}-ruby-${{ matrix.ruby }}-gems- - - name: Bundle - run: | - bundle config path vendor/bundle + name: Install dependencies + run: bundle install - - name: Build C extension + name: Build C extension and gem run: bundle exec rake build - name: Test @@ -112,3 +82,32 @@ jobs: bundle exec rake test || (echo "===== Retry Attempt: 2 ====" && bundle exec rake test) || \ (echo "===== Retry Attempt: 3 ====" && bundle exec rake test) + + lint: + env: + RUBY_VERSION: "3.0" + BUNDLE_WITHOUT: test + + name: Ruby linter + runs-on: ubuntu-latest + container: ruby:3.0 + steps: + - + name: Checkout + uses: actions/checkout@v2 + - + name: Cache + uses: actions/cache@v2 + with: + path: ${{env.BUNDLE_PATH}} + key: ${{ runner.os }}-ruby-${{ env.RUBY_VERSION }}-linter-gems-${{ hashFiles('**/Gemfile.lock') }} + restore-keys: | + ${{ runner.os }}-ruby-${{ env.RUBY_VERSION }}-linter-gems- + - + name: Bundle + if: steps.cache-primes.outputs.cache-hit != 'true' + run: bundle install + - + name: Linting + run: bundle exec rubocop + diff --git a/.rubocop.yml b/.rubocop.yml index 8a1afa36f..8168ee689 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,5 +1,19 @@ -inherit_from: - - http://shopify.github.io/ruby-style-guide/rubocop.yml +require: + - rubocop-minitest + - rubocop-rake + +inherit_from: .rubocop_todo.yml + +inherit_gem: + rubocop-shopify: rubocop.yml AllCops: - TargetRubyVersion: '2.6' + TargetRubyVersion: '2.7' + NewCops: enable + +Naming/InclusiveLanguage: + Enabled: false + +Style/GlobalVars: + Exclude: + - ext/semian/extconf.rb diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml new file mode 100644 index 000000000..2797937e3 --- /dev/null +++ b/.rubocop_todo.yml @@ -0,0 +1,15 @@ +# This configuration was generated by +# `rubocop --auto-gen-config` +# on 2022-05-19 18:59:22 UTC using RuboCop version 1.29.1. +# The point is for the user to remove these configuration records +# one by one as the offenses are removed from the code base. +# Note that changes in the inspected code, or installation of new +# versions of RuboCop, may require this file to be generated again. + +# Offense count: 4 +# Configuration parameters: AllowComments, AllowNil. +Lint/SuppressedException: + Exclude: + - 'test/helpers/circuit_breaker_helper.rb' + - 'test/net_http_test.rb' + - 'test/protected_resource_test.rb' diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 26353d5fe..7e252d702 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,5 +1,6 @@ { - "recommendations": [ - "ms-vscode-remote.vscode-remote-extensionpack" - ], + "recommendations": [ + "ms-vscode-remote.vscode-remote-extensionpack", + "EditorConfig.EditorConfig" + ] } diff --git a/Gemfile b/Gemfile index 9df9bd43f..2a2333630 100644 --- a/Gemfile +++ b/Gemfile @@ -1,20 +1,30 @@ -source 'https://rubygems.org' +# frozen_string_literal: true -gem 'benchmark-memory' -gem 'grpc' -gem 'hiredis', '~> 0.6' -gem 'memory_profiler' -gem 'minitest' -gem 'mocha' -gem 'mysql2', '~> 0.5', github: 'brianmario/mysql2' -gem 'pry-byebug', require: false -gem 'rake-compiler' -gem 'rake' -gem 'redis-client', '0.2.0' -gem 'redis' -gem 'rubocop' -gem 'timecop' -gem 'toxiproxy', '~> 1.0.0' -gem 'webrick' +source "https://rubygems.org" + +group :test do + gem "benchmark-memory" + gem "grpc" + gem "hiredis", "~> 0.6" + gem "memory_profiler" + gem "minitest" + gem "mocha" + gem "mysql2", "~> 0.5", github: "brianmario/mysql2" + gem "pry-byebug", require: false + gem "rake-compiler" + gem "rake" + gem "redis-client", "0.2.0" + gem "redis" + gem "timecop" + gem "toxiproxy", "~> 1.0.0" + gem "webrick" +end + +group :lint do + gem "rubocop-minitest", require: false + gem "rubocop-rake", require: false + gem "rubocop-shopify", require: false + gem "rubocop", require: false +end gemspec diff --git a/Gemfile.lock b/Gemfile.lock index f91349490..30466dc2c 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -62,6 +62,12 @@ GEM unicode-display_width (>= 1.4.0, < 3.0) rubocop-ast (1.18.0) parser (>= 3.1.1.0) + rubocop-minitest (0.19.1) + rubocop (>= 0.90, < 2.0) + rubocop-rake (0.6.0) + rubocop (~> 1.0) + rubocop-shopify (2.6.0) + rubocop (~> 1.29) ruby-progressbar (1.11.0) timecop (0.9.5) toxiproxy (1.0.3) @@ -86,6 +92,9 @@ DEPENDENCIES redis redis-client (= 0.2.0) rubocop + rubocop-minitest + rubocop-rake + rubocop-shopify semian! timecop toxiproxy (~> 1.0.0) diff --git a/Rakefile b/Rakefile index 2a8e36001..1ce2bd44f 100644 --- a/Rakefile +++ b/Rakefile @@ -1,17 +1,19 @@ -require 'bundler/gem_tasks' -begin - require 'rubocop/rake_task' - RuboCop::RakeTask.new -rescue LoadError +# frozen_string_literal: true + +require "bundler/gem_tasks" +require "rubocop/rake_task" +RuboCop::RakeTask.new do |task| + task.requires << "rubocop-minitest" + task.requires << "rubocop-rake" end # ========================================================== # Packaging # ========================================================== -GEMSPEC = eval(File.read('semian.gemspec')) +GEMSPEC = eval(File.read("semian.gemspec")) # rubocop:disable Security/Eval -require 'rubygems/package_task' +require "rubygems/package_task" Gem::PackageTask.new(GEMSPEC) do |_pkg| end @@ -19,17 +21,19 @@ end # Ruby Extension # ========================================================== -$LOAD_PATH.unshift File.expand_path("../lib", __FILE__) -require 'semian/platform' +$LOAD_PATH.unshift(File.expand_path("../lib", __FILE__)) +require "semian/platform" if Semian.sysv_semaphores_supported? - require 'rake/extensiontask' - Rake::ExtensionTask.new('semian', GEMSPEC) do |ext| - ext.ext_dir = 'ext/semian' - ext.lib_dir = 'lib/semian' + require "rake/extensiontask" + Rake::ExtensionTask.new("semian", GEMSPEC) do |ext| + ext.ext_dir = "ext/semian" + ext.lib_dir = "lib/semian" end + desc "Build gem" task build: :compile else - task :build do + desc "Build gem" + task :build do # rubocop:disable Rake/DuplicateTask end end @@ -37,23 +41,23 @@ end # Testing # ========================================================== -require 'rake/testtask' -Rake::TestTask.new 'test' do |t| - t.libs = %w(lib test) +require "rake/testtask" +Rake::TestTask.new("test") do |t| + t.libs = ["lib", "test"] t.pattern = "test/*_test.rb" t.warning = false if ENV["CI"] || ENV["VERBOSE"] - t.options = '-v' + t.options = "-v" end end # ========================================================== # Documentation # ========================================================== -require 'rdoc/task' +require "rdoc/task" RDoc::Task.new do |rdoc| rdoc.rdoc_files.include("lib/*.rb", "ext/semian/*.c") end task default: :build -task default: :test +task default: :test # rubocop:disable Rake/DuplicateTask diff --git a/ext/semian/extconf.rb b/ext/semian/extconf.rb index ac2c511be..274c2830f 100644 --- a/ext/semian/extconf.rb +++ b/ext/semian/extconf.rb @@ -1,33 +1,35 @@ -$LOAD_PATH.unshift File.expand_path("../../../lib", __FILE__) +# frozen_string_literal: true -require 'semian/platform' +$LOAD_PATH.unshift(File.expand_path("../../../lib", __FILE__)) + +require "semian/platform" unless Semian.sysv_semaphores_supported? - File.write "Makefile", <:mysql_shard0 that has 10 tickets and a default timeout of 500 milliseconds. +# This registers a new resource called :mysql_shard0 that has 10 tickets and +# a default timeout of 500 milliseconds. # # After 3 failures in the span of 10 seconds the circuit will be open. # After an additional 10 seconds it will transition to half-open. @@ -70,8 +80,8 @@ # # Perform a MySQL query here # end # -# This acquires a ticket for the :mysql_shard0 resource. If we use the example above, the ticket count would -# be lowered to 9 when block is executed, then raised to 10 when the block completes. +# This acquires a ticket for the :mysql_shard0 resource. If we use the example above, +# the ticket count would be lowered to 9 when block is executed, then raised to 10 when the block completes. # # ===== Overriding the default timeout # @@ -79,7 +89,8 @@ # # Perform a MySQL query here # end # -# This is the same as the previous example, but overrides the timeout from the default value of 500 milliseconds to 1 second. +# This is the same as the previous example, but overrides the timeout +# from the default value of 500 milliseconds to 1 second. module Semian extend self extend Instrumentable @@ -91,12 +102,14 @@ module Semian OpenCircuitError = Class.new(BaseError) attr_accessor :maximum_lru_size, :minimum_lru_time, :default_permissions, :namespace + self.maximum_lru_size = 500 self.minimum_lru_time = 300 self.default_permissions = 0660 def issue_disabled_semaphores_warning return if defined?(@warning_issued) + @warning_issued = true if !sysv_semaphores_supported? logger.info("Semian sysv semaphores are not supported on #{RUBY_PLATFORM} - all operations will no-op") @@ -119,7 +132,7 @@ def to_s attr_accessor :logger - self.logger = Logger.new(STDERR) + self.logger = Logger.new($stderr) # Registers a resource. # @@ -161,7 +174,7 @@ def register(name, **options) bulkhead = create_bulkhead(name, **options) if circuit_breaker.nil? && bulkhead.nil? - raise ArgumentError, 'Both bulkhead and circuitbreaker cannot be disabled.' + raise ArgumentError, "Both bulkhead and circuitbreaker cannot be disabled." end resources[name] = ProtectedResource.new(name, bulkhead, circuit_breaker) @@ -170,11 +183,10 @@ def register(name, **options) def retrieve_or_register(name, **args) # If consumer who retrieved / registered by a Semian::Adapter, keep track # of who the consumer was so that we can clear the resource reference if needed. - if consumer = args.delete(:consumer) - if consumer.class.include?(Semian::Adapter) - consumers[name] ||= [] - consumers[name] << WeakRef.new(consumer) - end + consumer = args.delete(:consumer) + if consumer&.class&.include?(Semian::Adapter) + consumers[name] ||= [] + consumers[name] << WeakRef.new(consumer) end self[name] || register(name, **args) end @@ -185,9 +197,8 @@ def [](name) end def destroy(name) - if resource = resources.delete(name) - resource.destroy - end + resource = resources.delete(name) + resource&.destroy end def destroy_all_resources @@ -204,17 +215,16 @@ def destroy_all_resources # Also clears any semian_resources # in use by any semian adapters if the weak reference is still alive. def unregister(name) - if resource = resources.delete(name) - resource.bulkhead.unregister_worker if resource.bulkhead + resource = resources.delete(name) + if resource + resource.bulkhead&.unregister_worker consumers_for_resource = consumers.delete(name) || [] consumers_for_resource.each do |consumer| - begin - if consumer.weakref_alive? - consumer.clear_semian_resource - end - rescue WeakRef::RefError - next + if consumer.weakref_alive? + consumer.clear_semian_resource end + rescue WeakRef::RefError + next end end end @@ -243,6 +253,7 @@ def reset! def thread_safe? return @thread_safe if defined?(@thread_safe) + @thread_safe = true end @@ -255,6 +266,7 @@ def thread_safe=(thread_safe) def create_circuit_breaker(name, **options) circuit_breaker = options.fetch(:circuit_breaker, true) return unless circuit_breaker + require_keys!([:success_threshold, :error_threshold, :error_timeout], options) exceptions = options[:exceptions] || [] @@ -277,8 +289,8 @@ def implementation(**options) unless options[:thread_safety_disabled].nil? logger.info( "NOTE: thread_safety_disabled will be replaced by a global setting" \ - "Semian is thread safe by default. It is possible" \ - "to modify the value by using Semian.thread_safe=", + "Semian is thread safe by default. It is possible" \ + "to modify the value by using Semian.thread_safe=", ) end @@ -304,13 +316,13 @@ def require_keys!(required, options) end if Semian.semaphores_enabled? - require 'semian/semian' + require "semian/semian" else Semian::MAX_TICKETS = 0 end if defined? ActiveSupport - ActiveSupport.on_load :active_record do - require 'semian/rails' + ActiveSupport.on_load(:active_record) do + require "semian/rails" end end diff --git a/lib/semian/adapter.rb b/lib/semian/adapter.rb index e78a746cd..fb2922266 100644 --- a/lib/semian/adapter.rb +++ b/lib/semian/adapter.rb @@ -1,9 +1,11 @@ -require 'semian' +# frozen_string_literal: true + +require "semian" module Semian module Adapter def semian_identifier - raise NotImplementedError.new("Semian adapters must implement a `semian_identifier` method") + raise NotImplementedError, "Semian adapters must implement a `semian_identifier` method" end def semian_resource @@ -31,6 +33,7 @@ def clear_semian_resource def acquire_semian_resource(scope:, adapter:, &block) return yield if resource_already_acquired? + semian_resource.acquire(scope: scope, adapter: adapter, resource: self) do mark_resource_as_acquired(&block) end @@ -48,16 +51,17 @@ def acquire_semian_resource(scope:, adapter:, &block) def semian_options return @semian_options if defined? @semian_options + options = raw_semian_options @semian_options = options && options.map { |k, v| [k.to_sym, v] }.to_h end def raw_semian_options - raise NotImplementedError.new("Semian adapters must implement a `raw_semian_options` method") + raise NotImplementedError, "Semian adapters must implement a `raw_semian_options` method" end def resource_exceptions - raise NotImplementedError.new("Semian adapters must implement a `resource_exceptions` method") + raise NotImplementedError, "Semian adapters must implement a `resource_exceptions` method" end def resource_already_acquired? diff --git a/lib/semian/circuit_breaker.rb b/lib/semian/circuit_breaker.rb index 90219e97a..643fed15c 100644 --- a/lib/semian/circuit_breaker.rb +++ b/lib/semian/circuit_breaker.rb @@ -1,5 +1,7 @@ +# frozen_string_literal: true + module Semian - class CircuitBreaker #:nodoc: + class CircuitBreaker # :nodoc: extend Forwardable def_delegators :@state, :closed?, :open?, :half_open? @@ -7,7 +9,9 @@ class CircuitBreaker #:nodoc: attr_reader :name, :half_open_resource_timeout, :error_timeout, :state, :last_error def initialize(name, exceptions:, success_threshold:, error_threshold:, - error_timeout:, implementation:, half_open_resource_timeout: nil, error_threshold_timeout: nil) + error_timeout:, implementation:, half_open_resource_timeout: nil, + error_threshold_timeout: nil) + @name = name.to_sym @success_count_threshold = success_threshold @error_count_threshold = error_threshold @@ -25,6 +29,7 @@ def initialize(name, exceptions:, success_threshold:, error_threshold:, def acquire(resource = nil, &block) return yield if disabled? + transition_to_half_open if transition_to_half_open? raise OpenCircuitError unless request_allowed? @@ -63,6 +68,7 @@ def mark_failed(error) def mark_success return unless half_open? + @successes.increment transition_to_close if success_threshold_reached? end @@ -80,8 +86,7 @@ def destroy end def in_use? - return false if error_timeout_expired? - @errors.size > 0 + !error_timeout_expired? && !@errors.empty? end private @@ -117,6 +122,7 @@ def error_threshold_reached? def error_timeout_expired? last_error_time = @errors.last return false unless last_error_time + Time.at(last_error_time) + @error_timeout < Time.now end @@ -133,12 +139,12 @@ def log_state_transition(new_state) return if @state.nil? || new_state == @state.value str = "[#{self.class.name}] State transition from #{@state.value} to #{new_state}." - str << " success_count=#{@successes.value} error_count=#{@errors.size}" - str << " success_count_threshold=#{@success_count_threshold} error_count_threshold=#{@error_count_threshold}" - str << " error_timeout=#{@error_timeout} error_last_at=\"#{@errors.last}\"" - str << " name=\"#{@name}\"" + str += " success_count=#{@successes.value} error_count=#{@errors.size}" + str += " success_count_threshold=#{@success_count_threshold} error_count_threshold=#{@error_count_threshold}" + str += " error_timeout=#{@error_timeout} error_last_at=\"#{@errors.last}\"" + str += " name=\"#{@name}\"" if new_state == :open && @last_error - str << " last_error_message=#{@last_error.message.inspect}" + str += " last_error_message=#{@last_error.message.inspect}" end Semian.logger.info(str) @@ -149,7 +155,7 @@ def notify_state_transition(new_state) end def disabled? - ENV['SEMIAN_CIRCUIT_BREAKER_DISABLED'] || ENV['SEMIAN_DISABLED'] + ENV["SEMIAN_CIRCUIT_BREAKER_DISABLED"] || ENV["SEMIAN_DISABLED"] end def maybe_with_half_open_resource_timeout(resource, &block) diff --git a/lib/semian/grpc.rb b/lib/semian/grpc.rb index 38acc20dc..d0e08f8b1 100644 --- a/lib/semian/grpc.rb +++ b/lib/semian/grpc.rb @@ -1,5 +1,7 @@ -require 'semian/adapter' -require 'grpc' +# frozen_string_literal: true + +require "semian/adapter" +require "grpc" module GRPC GRPC::Unavailable.include(::Semian::AdapterError) @@ -22,7 +24,6 @@ def initialize(semian_identifier, *args) module Semian module GRPC - attr_reader :raw_semian_options include Semian::Adapter ResourceBusyError = ::GRPC::ResourceBusyError @@ -40,6 +41,7 @@ class << self def semian_configuration=(configuration) raise Semian::GRPC::SemianConfigurationChangedError unless @semian_configuration.nil? + @semian_configuration = configuration end @@ -52,10 +54,10 @@ def raw_semian_options @raw_semian_options ||= begin # If the host is empty, it's possible that the adapter was initialized # with the channel. Therefore, we look into the channel to find the host - if @host.empty? - host = @ch.target + host = if @host.empty? + @ch.target else - host = @host + @host end @raw_semian_options = Semian::GRPC.retrieve_semian_configuration(host) @raw_semian_options = @raw_semian_options.dup unless @raw_semian_options.nil? @@ -81,21 +83,25 @@ def disabled? def request_response(*, **) return super if disabled? + acquire_semian_resource(adapter: :grpc, scope: :request_response) { super } end def client_streamer(*, **) return super if disabled? + acquire_semian_resource(adapter: :grpc, scope: :client_streamer) { super } end def server_streamer(*, **) return super if disabled? + acquire_semian_resource(adapter: :grpc, scope: :server_streamer) { super } end def bidi_streamer(*, **) return super if disabled? + acquire_semian_resource(adapter: :grpc, scope: :bidi_streamer) { super } end end diff --git a/lib/semian/instrumentable.rb b/lib/semian/instrumentable.rb index ed387d756..966be751a 100644 --- a/lib/semian/instrumentable.rb +++ b/lib/semian/instrumentable.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Semian module Instrumentable def subscribe(name = rand, &block) diff --git a/lib/semian/lru_hash.rb b/lib/semian/lru_hash.rb index 0cf1bbaf1..e5f236f69 100644 --- a/lib/semian/lru_hash.rb +++ b/lib/semian/lru_hash.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class LRUHash # This LRU (Least Recently Used) hash will allow # the cleaning of resources as time goes on. @@ -120,10 +122,10 @@ def [](key) def clear_unused_resources payload = { - size: @table.size, - examined: 0, - cleared: 0, - elapsed: nil, + size: @table.size, + examined: 0, + cleared: 0, + elapsed: nil, } timer_start = Process.clock_gettime(Process::CLOCK_MONOTONIC) @@ -155,20 +157,19 @@ def clear_unused_resources end end - EXCEPTION_NEVER = {Exception => :never}.freeze - EXCEPTION_IMMEDIATE = {Exception => :immediate}.freeze + EXCEPTION_NEVER = { Exception => :never }.freeze + EXCEPTION_IMMEDIATE = { Exception => :immediate }.freeze private_constant :EXCEPTION_NEVER private_constant :EXCEPTION_IMMEDIATE - def try_synchronize + def try_synchronize(&block) Thread.handle_interrupt(EXCEPTION_NEVER) do - begin - return false unless @lock.try_lock - Thread.handle_interrupt(EXCEPTION_IMMEDIATE) { yield } - true - ensure - @lock.unlock if @lock.owned? - end + return false unless @lock.try_lock + + Thread.handle_interrupt(EXCEPTION_IMMEDIATE, &block) + true + ensure + @lock.unlock if @lock.owned? end end end diff --git a/lib/semian/mysql2.rb b/lib/semian/mysql2.rb index 1ef1167aa..801b848a7 100644 --- a/lib/semian/mysql2.rb +++ b/lib/semian/mysql2.rb @@ -1,5 +1,7 @@ -require 'semian/adapter' -require 'mysql2' +# frozen_string_literal: true + +require "semian/adapter" +require "mysql2" module Mysql2 Mysql2::Error.include(::Semian::AdapterError) @@ -32,13 +34,13 @@ module Mysql2 CircuitOpenError = ::Mysql2::CircuitOpenError PingFailure = Class.new(::Mysql2::Error) - DEFAULT_HOST = 'localhost' + DEFAULT_HOST = "localhost" DEFAULT_PORT = 3306 QUERY_WHITELIST = Regexp.union( - /\A(?:\/\*.*?\*\/)?\s*ROLLBACK/i, - /\A(?:\/\*.*?\*\/)?\s*COMMIT/i, - /\A(?:\/\*.*?\*\/)?\s*RELEASE\s+SAVEPOINT/i, + %r{\A(?:/\*.*?\*/)?\s*ROLLBACK}i, + %r{\A(?:/\*.*?\*/)?\s*COMMIT}i, + %r{\A(?:/\*.*?\*/)?\s*RELEASE\s+SAVEPOINT}i, ) # The naked methods are exposed as `raw_query` and `raw_connect` for instrumentation purpose @@ -55,7 +57,8 @@ def self.included(base) def semian_identifier @semian_identifier ||= begin - unless name = semian_options && semian_options[:name] + name = semian_options && semian_options[:name] + unless name host = query_options[:host] || DEFAULT_HOST port = query_options[:port] || DEFAULT_PORT name = "#{host}:#{port}" @@ -68,7 +71,7 @@ def ping result = nil acquire_semian_resource(adapter: :mysql, scope: :ping) do result = raw_ping - raise PingFailure.new(result.to_s) unless result + raise PingFailure, result.to_s unless result end result rescue ResourceBusyError, CircuitOpenError, PingFailure @@ -113,6 +116,7 @@ def query_whitelisted?(sql, *) # data that is not recognized as a valid encoding, in which case we just # return false. return false unless sql.valid_encoding? + raise end @@ -132,7 +136,7 @@ def acquire_semian_resource(**) def raw_semian_options return query_options[:semian] if query_options.key?(:semian) - return query_options['semian'.freeze] if query_options.key?('semian'.freeze) + return query_options["semian"] if query_options.key?("semian") end end end diff --git a/lib/semian/net_http.rb b/lib/semian/net_http.rb index 158850ca7..01c621b3d 100644 --- a/lib/semian/net_http.rb +++ b/lib/semian/net_http.rb @@ -1,5 +1,7 @@ -require 'semian/adapter' -require 'net/http' +# frozen_string_literal: true + +require "semian/adapter" +require "net/http" module Net ProtocolError.include(::Semian::AdapterError) @@ -40,10 +42,11 @@ def semian_identifier ::Net::ProtocolError, ::EOFError, ::IOError, - ::SystemCallError, # includes ::Errno::EINVAL, ::Errno::ECONNRESET, ::Errno::ECONNREFUSED, ::Errno::ETIMEDOUT, and more + ::SystemCallError, # includes ::Errno::EINVAL, ::Errno::ECONNRESET, + # ::Errno::ECONNREFUSED, ::Errno::ETIMEDOUT, and more ].freeze # Net::HTTP can throw many different errors, this tries to capture most of them - module ClassMethods + module ClassMethods def new(*args, semian: true) http = super(*args) http.instance_variable_set(:@semian_enabled, semian) @@ -57,6 +60,7 @@ class << self def semian_configuration=(configuration) raise Semian::NetHTTP::SemianConfigurationChangedError unless @semian_configuration.nil? + @semian_configuration = configuration end @@ -88,11 +92,13 @@ def disabled? def connect return super if disabled? + acquire_semian_resource(adapter: :http, scope: :connection) { super } end def transport_request(*) return super if disabled? + acquire_semian_resource(adapter: :http, scope: :query) do handle_error_responses(super) end diff --git a/lib/semian/platform.rb b/lib/semian/platform.rb index 1442c0b34..897434b2a 100644 --- a/lib/semian/platform.rb +++ b/lib/semian/platform.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Semian extend self @@ -11,6 +13,6 @@ def semaphores_enabled? end def disabled? - ENV['SEMIAN_SEMAPHORES_DISABLED'] || ENV['SEMIAN_DISABLED'] + ENV["SEMIAN_SEMAPHORES_DISABLED"] || ENV["SEMIAN_DISABLED"] end end diff --git a/lib/semian/protected_resource.rb b/lib/semian/protected_resource.rb index 73c10fac9..13a5147a1 100644 --- a/lib/semian/protected_resource.rb +++ b/lib/semian/protected_resource.rb @@ -1,10 +1,12 @@ +# frozen_string_literal: true + module Semian class ProtectedResource extend Forwardable def_delegators :@bulkhead, :destroy, :count, :semid, :tickets, :registered_workers def_delegators :@circuit_breaker, :reset, :mark_failed, :mark_success, :request_allowed?, - :open?, :closed?, :half_open? + :open?, :closed?, :half_open? attr_reader :bulkhead, :circuit_breaker, :name attr_accessor :updated_at @@ -17,8 +19,8 @@ def initialize(name, bulkhead, circuit_breaker) end def destroy - @bulkhead.destroy unless @bulkhead.nil? - @circuit_breaker.destroy unless @circuit_breaker.nil? + @bulkhead&.destroy + @circuit_breaker&.destroy end def acquire(timeout: nil, scope: nil, adapter: nil, resource: nil) diff --git a/lib/semian/rails.rb b/lib/semian/rails.rb index 4c0aa8c5c..47ab3a2ca 100644 --- a/lib/semian/rails.rb +++ b/lib/semian/rails.rb @@ -1,9 +1,15 @@ -require 'active_record/connection_adapters/abstract_adapter' +# frozen_string_literal: true -class ActiveRecord::ConnectionAdapters::AbstractAdapter - def semian_resource - # support for https://github.com/rails/rails/commit/d86fd6415c0dfce6fadb77e74696cf728e5eb76b - connection = instance_variable_defined?(:@raw_connection) ? @raw_connection : @connection - connection.semian_resource +require "active_record/connection_adapters/abstract_adapter" + +module ActiveRecord + module ConnectionAdapters + class AbstractAdapter + def semian_resource + # support for https://github.com/rails/rails/commit/d86fd6415c0dfce6fadb77e74696cf728e5eb76b + connection = instance_variable_defined?(:@raw_connection) ? @raw_connection : @connection + connection.semian_resource + end + end end end diff --git a/lib/semian/redis.rb b/lib/semian/redis.rb index d8da64afa..5b66ba508 100644 --- a/lib/semian/redis.rb +++ b/lib/semian/redis.rb @@ -1,5 +1,7 @@ -require 'semian/adapter' -require 'redis' +# frozen_string_literal: true + +require "semian/adapter" +require "redis" class Redis Redis::BaseConnectionError.include(::Semian::AdapterError) @@ -17,7 +19,7 @@ class OutOfMemoryError < Redis::CommandError end class ConnectionError < Redis::BaseConnectionError - # A Connection Reset is a fast failure and we don't want to track these errors in + # A Connection Reset is a fast failure and we don't want to track these errors in # semian def marks_semian_circuits? message != "Connection lost (ECONNRESET)" @@ -90,12 +92,11 @@ def io(&block) def connect acquire_semian_resource(adapter: :redis, scope: :connection) do - begin - raw_connect - rescue SocketError, RuntimeError => e - raise ResolveError.new(semian_identifier) if dns_resolve_failure?(e.cause || e) - raise - end + raw_connect + rescue SocketError, RuntimeError => e + raise ResolveError, semian_identifier if dns_resolve_failure?(e.cause || e) + + raise end end @@ -108,7 +109,7 @@ def with_resource_timeout(temp_timeout) begin connection.timeout = temp_timeout if connected? options[:timeout] = Float(temp_timeout), - options[:connect_timeout] = Float(temp_timeout) + options[:connect_timeout] = Float(temp_timeout) options[:read_timeout] = Float(temp_timeout) options[:write_timeout] = Float(temp_timeout) yield @@ -133,17 +134,18 @@ def resource_exceptions def raw_semian_options return options[:semian] if options.key?(:semian) - return options['semian'.freeze] if options.key?('semian'.freeze) + return options["semian"] if options.key?("semian") end def raise_if_out_of_memory(reply) return unless reply.is_a?(::Redis::CommandError) return unless reply.message.start_with?("OOM ") - raise ::Redis::OutOfMemoryError.new(reply.message) + + raise ::Redis::OutOfMemoryError, reply.message end def dns_resolve_failure?(e) - e.to_s.match?(/(can't resolve)|(name or service not known)|(nodename nor servname provided, or not known)|(failure in name resolution)/i) + e.to_s.match?(/(can't resolve)|(name or service not known)|(nodename nor servname provided, or not known)|(failure in name resolution)/i) # rubocop:disable Layout/LineLength end end end diff --git a/lib/semian/redis_client.rb b/lib/semian/redis_client.rb index 2e9bb0e3b..257966bcf 100644 --- a/lib/semian/redis_client.rb +++ b/lib/semian/redis_client.rb @@ -1,5 +1,7 @@ -require 'semian/adapter' -require 'redis-client' +# frozen_string_literal: true + +require "semian/adapter" +require "redis-client" class RedisClient ConnectionError.include(::Semian::AdapterError) @@ -104,7 +106,7 @@ module RedisClientPool include RedisClientCommon define_method(:semian_resource, Semian::Adapter.instance_method(:semian_resource)) define_method(:clear_semian_resource, Semian::Adapter.instance_method(:clear_semian_resource)) - end + end end RedisClient.prepend(Semian::RedisClient) diff --git a/lib/semian/resource.rb b/lib/semian/resource.rb index 6a6e3429e..316b20b7f 100644 --- a/lib/semian/resource.rb +++ b/lib/semian/resource.rb @@ -1,6 +1,8 @@ +# frozen_string_literal: true + module Semian - class Resource #:nodoc: - attr_reader :tickets, :name + class Resource # :nodoc: + attr_reader :name class << Semian::Resource # Ensure that there can only be one resource of a given type @@ -55,7 +57,7 @@ def semid end def key - '0x00000000' + "0x00000000" end def in_use? diff --git a/lib/semian/simple_integer.rb b/lib/semian/simple_integer.rb index 51bc498bb..3d8708808 100644 --- a/lib/semian/simple_integer.rb +++ b/lib/semian/simple_integer.rb @@ -1,8 +1,10 @@ -require 'thread' +# frozen_string_literal: true + +require "thread" module Semian module Simple - class Integer #:nodoc: + class Integer # :nodoc: attr_accessor :value def initialize diff --git a/lib/semian/simple_sliding_window.rb b/lib/semian/simple_sliding_window.rb index 55a2bb713..64c530f4f 100644 --- a/lib/semian/simple_sliding_window.rb +++ b/lib/semian/simple_sliding_window.rb @@ -1,11 +1,13 @@ -require 'thread' +# frozen_string_literal: true + +require "thread" module Semian module Simple - class SlidingWindow #:nodoc: + class SlidingWindow # :nodoc: extend Forwardable - def_delegators :@window, :size, :last + def_delegators :@window, :size, :last, :empty? attr_reader :max_size # A sliding window is a structure that stores the most @max_size recent timestamps diff --git a/lib/semian/simple_state.rb b/lib/semian/simple_state.rb index 322d333d1..2b414956f 100644 --- a/lib/semian/simple_state.rb +++ b/lib/semian/simple_state.rb @@ -1,6 +1,8 @@ +# frozen_string_literal: true + module Semian module Simple - class State #:nodoc: + class State # :nodoc: def initialize reset end diff --git a/lib/semian/unprotected_resource.rb b/lib/semian/unprotected_resource.rb index 8a88b8268..a6ee4d45c 100644 --- a/lib/semian/unprotected_resource.rb +++ b/lib/semian/unprotected_resource.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Semian # This class acts as a replacement for `ProtectedResource` when # the semian configuration of an `Adapter` is missing or explicitly disabled diff --git a/lib/semian/version.rb b/lib/semian/version.rb index d7bd8200e..eb22be1ff 100644 --- a/lib/semian/version.rb +++ b/lib/semian/version.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Semian - VERSION = '0.12.0' + VERSION = "0.12.0" end diff --git a/semian.gemspec b/semian.gemspec index 2838a6d63..55404f020 100644 --- a/semian.gemspec +++ b/semian.gemspec @@ -1,23 +1,25 @@ -$LOAD_PATH.unshift File.expand_path('../lib', __FILE__) +# frozen_string_literal: true -require 'semian/version' -require 'semian/platform' +$LOAD_PATH.unshift(File.expand_path("../lib", __FILE__)) + +require "semian/version" +require "semian/platform" Gem::Specification.new do |s| - s.name = 'semian' + s.name = "semian" s.version = Semian::VERSION - s.summary = 'Bulkheading for Ruby with SysV semaphores' + s.summary = "Bulkheading for Ruby with SysV semaphores" s.description = <<-DOC A Ruby C extention that is used to control access to shared resources across process boundaries with SysV semaphores. DOC - s.homepage = 'https://github.com/shopify/semian' - s.authors = ['Scott Francis', 'Simon Eskildsen', 'Dale Hamel'] - s.email = 'scott.francis@shopify.com' - s.license = 'MIT' + s.homepage = "https://github.com/shopify/semian" + s.authors = ["Scott Francis", "Simon Eskildsen", "Dale Hamel"] + s.email = "opensource@shopify.com" + s.license = "MIT" - s.metadata['allowed_push_host'] = 'https://rubygems.org' + s.metadata["allowed_push_host"] = "https://rubygems.org" - s.files = Dir['{lib,ext}/**/**/*.{rb,h,c}'] - s.extensions = ['ext/semian/extconf.rb'] + s.files = Dir["{lib,ext}/**/**/*.{rb,h,c}"] + s.extensions = ["ext/semian/extconf.rb"] end diff --git a/test/adapter_test.rb b/test/adapter_test.rb index fe9198eaf..1f86a2efb 100644 --- a/test/adapter_test.rb +++ b/test/adapter_test.rb @@ -1,4 +1,6 @@ -require 'test_helper' +# frozen_string_literal: true + +require "test_helper" class TestSemianAdapter < Minitest::Test def setup @@ -27,11 +29,11 @@ def test_unregister resource = Semian.register(:testing_unregister, tickets: 2, error_threshold: 0, error_timeout: 0, success_threshold: 0) assert_equal(Semian.resources[:testing_unregister], resource) - assert_equal 1, resource.registered_workers + assert_equal(1, resource.registered_workers) without_gc do Semian.unregister(:testing_unregister) - assert_equal 0, resource.registered_workers + assert_equal(0, resource.registered_workers) assert_empty(Semian.resources) assert_empty(Semian.consumers) @@ -54,7 +56,7 @@ def test_unregister_all_resources assert_equal(resource, client.semian_resource) Semian.unregister_all_resources - assert Semian.resources.empty? + assert_empty(Semian.resources) assert_empty(Semian.consumers) # The first call to client.semian_resource after unregistering all resources, @@ -70,15 +72,15 @@ def test_consumer_registration_does_not_prevent_gc # Release the only strong reference to the client object # so that it will be cleared on the forced GC run below - client = nil + client = nil # rubocop:disable Lint/UselessAssignment weak_ref = Semian.consumers[identifier].first - assert_equal(true, weak_ref.weakref_alive?) + assert_predicate(weak_ref, :weakref_alive?) GC.start(full_mark: true, immediate_sweep: true) - assert_nil weak_ref.weakref_alive? + assert_nil(weak_ref.weakref_alive?) - assert_raises WeakRef::RefError do + assert_raises(WeakRef::RefError) do weak_ref.any_method_call end end diff --git a/test/benchmark/lru_benchmarker.rb b/test/benchmark/lru_benchmarker.rb index 78d1e2bfc..146ca827e 100644 --- a/test/benchmark/lru_benchmarker.rb +++ b/test/benchmark/lru_benchmarker.rb @@ -1,18 +1,20 @@ +# frozen_string_literal: true + # Benchmarks the usage of an LRUHash during the set operation. # To make sure we are cleaning resources, MINIMUM_TIME_IN_LRU needs # to be set to 0 -$LOAD_PATH.unshift File.expand_path('../../../lib', __FILE__) -$LOAD_PATH.unshift File.expand_path('../../../test', __FILE__) -require 'thin' -require 'benchmark' -require 'benchmark/ips' -require 'benchmark/memory' -require 'semian' -require 'semian/net_http' -require 'toxiproxy' -require 'yaml' -require 'byebug' -require 'minitest' +$LOAD_PATH.unshift(File.expand_path("../../../lib", __FILE__)) +$LOAD_PATH.unshift(File.expand_path("../../../test", __FILE__)) +require "thin" +require "benchmark" +require "benchmark/ips" +require "benchmark/memory" +require "semian" +require "semian/net_http" +require "toxiproxy" +require "yaml" +require "byebug" +require "minitest" class LRUBenchmarker def run_ips_benchmark @@ -45,9 +47,11 @@ def create_request(i) # make it a success or a failure random = rand(1...100) if random >= 0 && random <= 50 - Semian.register("testing_#{i}", bulkhead: true, tickets: 1, error_threshold: 2, error_timeout: 5, success_threshold: 1) + Semian.register("testing_#{i}", bulkhead: true, tickets: 1, error_threshold: 2, error_timeout: 5, + success_threshold: 1) else - Semian.register("testing_#{i}", bulkhead: false, tickets: 1, error_threshold: 2, error_timeout: 5, success_threshold: 1) + Semian.register("testing_#{i}", bulkhead: false, tickets: 1, error_threshold: 2, error_timeout: 5, + success_threshold: 1) end end diff --git a/test/circuit_breaker_test.rb b/test/circuit_breaker_test.rb index 146419f31..0d18253eb 100644 --- a/test/circuit_breaker_test.rb +++ b/test/circuit_breaker_test.rb @@ -1,29 +1,32 @@ -require 'test_helper' +# frozen_string_literal: true + +require "test_helper" class TestCircuitBreaker < Minitest::Test include CircuitBreakerHelper def setup @strio = StringIO.new - Semian.logger = Logger.new @strio + Semian.logger = Logger.new(@strio) begin Semian.destroy(:testing) rescue nil end - Semian.register(:testing, tickets: 1, exceptions: [SomeError], error_threshold: 2, error_timeout: 5, success_threshold: 1) + Semian.register(:testing, tickets: 1, exceptions: [SomeError], error_threshold: 2, error_timeout: 5, + success_threshold: 1) @resource = Semian[:testing] end def test_acquire_yield_when_the_circuit_is_closed block_called = false @resource.acquire { block_called = true } - assert_equal true, block_called + assert(block_called) end def test_acquire_raises_circuit_open_error_when_the_circuit_is_open open_circuit! - assert_raises Semian::OpenCircuitError do + assert_raises(Semian::OpenCircuitError) do @resource.acquire { 1 + 1 } end assert_match(/State transition from closed to open/, @strio.string) @@ -78,7 +81,8 @@ def test_errors_more_than_duration_apart_doesnt_open_circuit end def test_sparse_errors_dont_open_circuit - resource = Semian.register(:three, tickets: 1, exceptions: [SomeError], error_threshold: 3, error_timeout: 5, success_threshold: 1) + resource = Semian.register(:three, tickets: 1, exceptions: [SomeError], error_threshold: 3, error_timeout: 5, + success_threshold: 1) Timecop.travel(-6) do trigger_error!(resource) @@ -100,16 +104,17 @@ def test_request_allowed_query_doesnt_trigger_transitions Timecop.travel(Time.now - 6) do open_circuit! - refute_predicate @resource, :request_allowed? - assert_predicate @resource, :open? + refute_predicate(@resource, :request_allowed?) + assert_predicate(@resource, :open?) end - assert_predicate @resource, :request_allowed? - assert_predicate @resource, :open? + assert_predicate(@resource, :request_allowed?) + assert_predicate(@resource, :open?) end def test_open_close_open_cycle - resource = Semian.register(:open_close, tickets: 1, exceptions: [SomeError], error_threshold: 2, error_timeout: 5, success_threshold: 2) + resource = Semian.register(:open_close, tickets: 1, exceptions: [SomeError], error_threshold: 2, error_timeout: 5, + success_threshold: 2) open_circuit!(resource) assert_circuit_opened(resource) @@ -117,10 +122,10 @@ def test_open_close_open_cycle Timecop.travel(resource.circuit_breaker.error_timeout + 1) do assert_circuit_closed(resource) - assert resource.half_open? + assert_predicate(resource, :half_open?) assert_circuit_closed(resource) - assert resource.closed? + assert_predicate(resource, :closed?) open_circuit!(resource) assert_circuit_opened(resource) @@ -128,16 +133,17 @@ def test_open_close_open_cycle Timecop.travel(resource.circuit_breaker.error_timeout + 1) do assert_circuit_closed(resource) - assert resource.half_open? + assert_predicate(resource, :half_open?) assert_circuit_closed(resource) - assert resource.closed? + assert_predicate(resource, :closed?) end end end def test_error_error_threshold_timeout_overrides_error_timeout_when_set_for_opening_circuits - resource = Semian.register(:three, tickets: 1, exceptions: [SomeError], error_threshold: 3, error_timeout: 5, success_threshold: 1, error_threshold_timeout: 10) + resource = Semian.register(:three, tickets: 1, exceptions: [SomeError], error_threshold: 3, error_timeout: 5, + success_threshold: 1, error_threshold_timeout: 10) Timecop.travel(-6) do trigger_error!(resource) @@ -156,7 +162,8 @@ def test_error_error_threshold_timeout_overrides_error_timeout_when_set_for_open end def test_error_threshold_timeout_defaults_to_error_timeout_when_not_specified - resource = Semian.register(:three, tickets: 1, exceptions: [SomeError], error_threshold: 3, error_timeout: 5, success_threshold: 1) + resource = Semian.register(:three, tickets: 1, exceptions: [SomeError], error_threshold: 3, error_timeout: 5, + success_threshold: 1) Timecop.travel(-6) do trigger_error!(resource) @@ -175,19 +182,19 @@ def test_error_threshold_timeout_defaults_to_error_timeout_when_not_specified end def test_env_var_disables_circuit_breaker - ENV['SEMIAN_CIRCUIT_BREAKER_DISABLED'] = '1' + ENV["SEMIAN_CIRCUIT_BREAKER_DISABLED"] = "1" open_circuit! assert_circuit_closed ensure - ENV.delete('SEMIAN_CIRCUIT_BREAKER_DISABLED') + ENV.delete("SEMIAN_CIRCUIT_BREAKER_DISABLED") end def test_semian_wide_env_var_disables_circuit_breaker - ENV['SEMIAN_DISABLED'] = '1' + ENV["SEMIAN_DISABLED"] = "1" open_circuit! assert_circuit_closed ensure - ENV.delete('SEMIAN_DISABLED') + ENV.delete("SEMIAN_DISABLED") end class RawResource @@ -206,8 +213,8 @@ def with_resource_timeout(timeout) def test_changes_resource_timeout_when_configured Semian.register(:resource_timeout, tickets: 1, exceptions: [SomeError], - error_threshold: 2, error_timeout: 5, success_threshold: 1, - half_open_resource_timeout: 0.123) + error_threshold: 2, error_timeout: 5, success_threshold: 1, + half_open_resource_timeout: 0.123) resource = Semian[:resource_timeout] half_open_cicuit!(resource) @@ -217,17 +224,17 @@ def test_changes_resource_timeout_when_configured triggered = false resource.acquire(resource: raw_resource) do triggered = true - assert_equal 0.123, raw_resource.timeout + assert_in_delta(0.123, raw_resource.timeout) end - assert triggered - assert_equal 2, raw_resource.timeout + assert(triggered) + assert_equal(2, raw_resource.timeout) end def test_doesnt_change_resource_timeout_when_closed Semian.register(:resource_timeout, tickets: 1, exceptions: [SomeError], - error_threshold: 2, error_timeout: 5, success_threshold: 1, - half_open_resource_timeout: 0.123) + error_threshold: 2, error_timeout: 5, success_threshold: 1, + half_open_resource_timeout: 0.123) resource = Semian[:resource_timeout] raw_resource = RawResource.new @@ -235,17 +242,17 @@ def test_doesnt_change_resource_timeout_when_closed triggered = false resource.acquire(resource: raw_resource) do triggered = true - assert_equal 2, raw_resource.timeout + assert_equal(2, raw_resource.timeout) end - assert triggered - assert_equal 2, raw_resource.timeout + assert(triggered) + assert_equal(2, raw_resource.timeout) end def test_doesnt_blow_up_when_configured_half_open_timeout_but_adapter_doesnt_support Semian.register(:resource_timeout, tickets: 1, exceptions: [SomeError], - error_threshold: 2, error_timeout: 5, success_threshold: 1, - half_open_resource_timeout: 0.123) + error_threshold: 2, error_timeout: 5, success_threshold: 1, + half_open_resource_timeout: 0.123) resource = Semian[:resource_timeout] raw_resource = Object.new @@ -255,7 +262,7 @@ def test_doesnt_blow_up_when_configured_half_open_timeout_but_adapter_doesnt_sup triggered = true end - assert triggered + assert(triggered) end class SomeErrorThatMarksCircuits < SomeError @@ -286,13 +293,13 @@ def test_notify_state_transition events = [] Semian.subscribe(:test_notify_state_transition) do |event, resource, _scope, _adapter, payload| if event == :state_change - events << {name: resource.name, state: payload[:state]} + events << { name: resource.name, state: payload[:state] } end end # Creating a resource should generate a :closed notification. resource = Semian.register(name, tickets: 1, exceptions: [StandardError], - error_threshold: 2, error_timeout: 1, success_threshold: 1) + error_threshold: 2, error_timeout: 1, success_threshold: 1) assert_equal(1, events.length) assert_equal(name, events[0][:name]) assert_equal(:closed, events[0][:state]) diff --git a/test/config/semian_config.rb b/test/config/semian_config.rb index 16c7df44d..eed2caa84 100644 --- a/test/config/semian_config.rb +++ b/test/config/semian_config.rb @@ -1,7 +1,9 @@ -require 'yaml' +# frozen_string_literal: true + +require "yaml" class SemianConfig - CONFIG_FILE = File.expand_path('../hosts.yml', __FILE__) + CONFIG_FILE = File.expand_path("../hosts.yml", __FILE__) class << self def [](service) diff --git a/test/echo_pb.rb b/test/echo_pb.rb index c62adc075..e92388434 100644 --- a/test/echo_pb.rb +++ b/test/echo_pb.rb @@ -1,7 +1,9 @@ +# frozen_string_literal: true + # Generated by the protocol buffer compiler. DO NOT EDIT! # source: echo.proto -require 'google/protobuf' +require "google/protobuf" Google::Protobuf::DescriptorPool.generated_pool.build do add_message "echo.EchoRequest" do diff --git a/test/echo_service.rb b/test/echo_service.rb index f0d735c74..d5e451f88 100644 --- a/test/echo_service.rb +++ b/test/echo_service.rb @@ -1,7 +1,9 @@ +# frozen_string_literal: true + # A test service with an echo implementation. class EchoMsg def self.marshal(_o) - '' + "" end def self.unmarshal(_o) @@ -23,7 +25,7 @@ def initialize(**kw) end def an_rpc(req, call) - GRPC.logger.info('echo service received a request') + GRPC.logger.info("echo service received a request") call.output_metadata.update(@trailing_metadata) @received_md << call.metadata unless call.metadata.nil? req diff --git a/test/echo_services_pb.rb b/test/echo_services_pb.rb index 8e23165a9..6fd93fbbb 100644 --- a/test/echo_services_pb.rb +++ b/test/echo_services_pb.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Generated by the protocol buffer compiler. DO NOT EDIT! # Source: echo.proto for package 'echo' # Original file comments: @@ -16,8 +18,8 @@ # limitations under the License. # -require 'grpc' -require 'echo_pb' +require "grpc" +require "echo_pb" module Echo module EchoServer @@ -26,7 +28,7 @@ class Service self.marshal_class_method = :encode self.unmarshal_class_method = :decode - self.service_name = 'echo.EchoServer' + self.service_name = "echo.EchoServer" rpc :Echo, EchoRequest, EchoReply end diff --git a/test/grpc_test.rb b/test/grpc_test.rb index 9350535e4..450f36928 100644 --- a/test/grpc_test.rb +++ b/test/grpc_test.rb @@ -1,9 +1,11 @@ -require 'test_helper' -require 'grpc' -require 'semian/grpc' -require 'minitest' -require 'mocha/minitest' -require 'echo_service' +# frozen_string_literal: true + +require "test_helper" +require "grpc" +require "semian/grpc" +require "minitest" +require "mocha/minitest" +require "echo_service" class TestGRPC < Minitest::Test ERROR_THRESHOLD = 1 @@ -17,7 +19,11 @@ class TestGRPC < Minitest::Test } DEFAULT_SEMIAN_CONFIGURATION = proc do |host| - next nil if host == SemianConfig['toxiproxy_upstream_host'] && port == SemianConfig['toxiproxy_upstream_port'] # disable if toxiproxy + if host == SemianConfig["toxiproxy_upstream_host"] && \ + port == SemianConfig["toxiproxy_upstream_port"] # disable if toxiproxy + next nil + end + SEMIAN_OPTIONS.merge(name: host) end @@ -34,15 +40,15 @@ def teardown end def test_semian_identifier - assert_equal @host, @stub.semian_identifier + assert_equal(@host, @stub.semian_identifier) end def test_errors_are_tagged_with_the_resource_identifier GRPC::ActiveCall.any_instance.stubs(:request_response).raises(::GRPC::Unavailable) - error = assert_raises ::GRPC::Unavailable do + error = assert_raises(::GRPC::Unavailable) do @stub.an_rpc(EchoMsg.new) end - assert_equal @host, error.semian_identifier + assert_equal(@host, error.semian_identifier) end def test_rpc_server @@ -55,29 +61,30 @@ def test_rpc_server def test_unavailable_server_opens_the_circuit GRPC::ActiveCall.any_instance.stubs(:request_response).raises(::GRPC::Unavailable) ERROR_THRESHOLD.times do - assert_raises ::GRPC::Unavailable do + assert_raises(::GRPC::Unavailable) do @stub.an_rpc(EchoMsg.new) end end - assert_raises GRPC::CircuitOpenError do + assert_raises(GRPC::CircuitOpenError) do @stub.an_rpc(EchoMsg.new) end end def test_timeout_opens_the_circuit skip if ENV["SKIP_FLAKY_TESTS"] - stub = build_insecure_stub(EchoStub, host: "#{SemianConfig['toxiproxy_upstream_host']}:#{SemianConfig['grpc_toxiproxy_port']}", opts: {timeout: 0.1}) + stub = build_insecure_stub(EchoStub, + host: "#{SemianConfig["toxiproxy_upstream_host"]}:#{SemianConfig["grpc_toxiproxy_port"]}", opts: { timeout: 0.1 }) run_services_on_server(@server, services: [EchoService]) do - Toxiproxy['semian_test_grpc'].downstream(:latency, latency: 1000).apply do + Toxiproxy["semian_test_grpc"].downstream(:latency, latency: 1000).apply do ERROR_THRESHOLD.times do - assert_raises GRPC::DeadlineExceeded do + assert_raises(GRPC::DeadlineExceeded) do stub.an_rpc(EchoMsg.new) end end end - Toxiproxy['semian_test_grpc'].downstream(:latency, latency: 1000).apply do - assert_raises GRPC::CircuitOpenError do + Toxiproxy["semian_test_grpc"].downstream(:latency, latency: 1000).apply do + assert_raises(GRPC::CircuitOpenError) do stub.an_rpc(EchoMsg.new) end end @@ -90,9 +97,9 @@ def test_instrumentation next if event != :success notified = true - assert_equal Semian[@host], resource - assert_equal :request_response, scope - assert_equal :grpc, adapter + assert_equal(Semian[@host], resource) + assert_equal(:request_response, scope) + assert_equal(:grpc, adapter) end run_services_on_server(@server, services: [EchoService]) do @@ -100,7 +107,7 @@ def test_instrumentation @stub.an_rpc(EchoMsg.new) end - assert notified, 'No notifications has been emitted' + assert(notified, "No notifications has been emitted") ensure Semian.unsubscribe(subscriber) end @@ -110,7 +117,7 @@ def test_circuit_breaker_on_client_streamer requests = [EchoMsg.new, EchoMsg.new] open_circuit!(stub, :a_client_streaming_rpc, requests) - assert_raises GRPC::CircuitOpenError do + assert_raises(GRPC::CircuitOpenError) do stub.a_client_streaming_rpc(requests) end end @@ -120,7 +127,7 @@ def test_circuit_breaker_on_server_streamer request = EchoMsg.new open_circuit!(stub, :a_server_streaming_rpc, request) - assert_raises GRPC::CircuitOpenError do + assert_raises(GRPC::CircuitOpenError) do stub.a_server_streaming_rpc(request) end end @@ -130,7 +137,7 @@ def test_circuit_breaker_on_bidi_streamer requests = [EchoMsg.new, EchoMsg.new] open_circuit!(stub, :a_bidi_rpc, requests) - assert_raises GRPC::CircuitOpenError do + assert_raises(GRPC::CircuitOpenError) do stub.a_bidi_rpc(requests) end end @@ -142,7 +149,7 @@ def test_bulkheads_tickets_are_working success_threshold: 1, error_threshold: 3, error_timeout: 10, - name: "#{host}", + name: host.to_s, } end Semian::GRPC.instance_variable_set(:@semian_configuration, nil) @@ -154,7 +161,7 @@ def test_bulkheads_tickets_are_working stub1.semian_resource.acquire do stub2 = build_insecure_stub(EchoStub, host: "0.0.0.1") stub2.semian_resource.acquire do - assert_raises GRPC::ResourceBusyError do + assert_raises(GRPC::ResourceBusyError) do stub2.an_rpc(EchoMsg.new) end end @@ -166,7 +173,7 @@ def test_bulkheads_tickets_are_working def open_circuit!(stub, method, args) ERROR_THRESHOLD.times do - assert_raises GRPC::Unavailable do + assert_raises(GRPC::Unavailable) do stub.send(method, args) end end @@ -179,9 +186,9 @@ def build_insecure_stub(klass, host: nil, opts: nil) end def build_rpc_server(server_opts: {}, client_opts: {}) - @hostname = SemianConfig['grpc_host'] - @server = new_rpc_server_for_testing({poll_period: 1}.merge(server_opts)) - @port = @server.add_http2_port("0.0.0.0:#{SemianConfig['grpc_port']}", :this_port_is_insecure) + @hostname = SemianConfig["grpc_host"] + @server = new_rpc_server_for_testing({ poll_period: 1 }.merge(server_opts)) + @port = @server.add_http2_port("0.0.0.0:#{SemianConfig["grpc_port"]}", :this_port_is_insecure) @host = "#{@hostname}:#{@port}" @client_opts = client_opts @server diff --git a/test/helpers/adapter_helper.rb b/test/helpers/adapter_helper.rb index b55ef4994..597ec7972 100644 --- a/test/helpers/adapter_helper.rb +++ b/test/helpers/adapter_helper.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Semian module AdapterTest include Semian::Adapter @@ -20,8 +22,8 @@ class AdapterTestClient def initialize(**args) @client_options = args.merge(success_threshold: 1, - error_threshold: 1, - error_timeout: 1) + error_threshold: 1, + error_timeout: 1) end def ==(other) diff --git a/test/helpers/background_helper.rb b/test/helpers/background_helper.rb index 13c0d8f8d..83c1dd68c 100644 --- a/test/helpers/background_helper.rb +++ b/test/helpers/background_helper.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module BackgroundHelper attr_writer :threads diff --git a/test/helpers/circuit_breaker_helper.rb b/test/helpers/circuit_breaker_helper.rb index 69e29bed7..0e328fc96 100644 --- a/test/helpers/circuit_breaker_helper.rb +++ b/test/helpers/circuit_breaker_helper.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module CircuitBreakerHelper SomeError = Class.new(StandardError) @@ -21,7 +23,7 @@ def trigger_error!(resource = @resource, error = SomeError) def assert_circuit_closed(resource = @resource) block_called = false resource.acquire { block_called = true } - assert block_called, 'Expected the circuit to be closed, but it was open' + assert(block_called, "Expected the circuit to be closed, but it was open") end def assert_circuit_opened(resource = @resource) @@ -31,6 +33,6 @@ def assert_circuit_opened(resource = @resource) rescue Semian::OpenCircuitError open = true end - assert open, 'Expected the circuit to be open, but it was closed' + assert(open, "Expected the circuit to be open, but it was closed") end end diff --git a/test/helpers/mock_server.rb b/test/helpers/mock_server.rb index 7b28942b5..a11b0a4fa 100644 --- a/test/helpers/mock_server.rb +++ b/test/helpers/mock_server.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'webrick' +require "webrick" class MockServer class << self @@ -41,10 +41,10 @@ def start_server response_code = 200 if response_code == "" res.status = response_code - res.content_type = 'text/html' + res.content_type = "text/html" rescue WEBrick::HTTPStatus::EOFError, WEBrick::HTTPStatus::BadRequest res.status = 400 - res.content_type = 'text/html' + res.content_type = "text/html" ensure res.send_response(sock) end diff --git a/test/helpers/resource_helper.rb b/test/helpers/resource_helper.rb index 59658acfb..4d640da81 100644 --- a/test/helpers/resource_helper.rb +++ b/test/helpers/resource_helper.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ResourceHelper private @@ -10,12 +12,11 @@ def create_resource(*args) def destroy_resources return unless @resources + @resources.each do |resource| - begin - resource.destroy - rescue - nil - end + resource.destroy + rescue + nil end @resources = [] end diff --git a/test/instrumentation_test.rb b/test/instrumentation_test.rb index 5b466d4f1..19cef6156 100644 --- a/test/instrumentation_test.rb +++ b/test/instrumentation_test.rb @@ -1,4 +1,6 @@ -require 'test_helper' +# frozen_string_literal: true + +require "test_helper" class TestInstrumentation < Minitest::Test def setup @@ -9,7 +11,7 @@ def setup def test_busy_instrumentation assert_notify(:success, :busy, :state_change) do Semian[:testing].acquire do - assert_raises Semian::TimeoutError do + assert_raises(Semian::TimeoutError) do Semian[:testing].acquire {} end end @@ -19,14 +21,14 @@ def test_busy_instrumentation def test_circuit_open_instrumentation assert_notify(:success, :busy, :state_change) do Semian[:testing].acquire do - assert_raises Semian::TimeoutError do + assert_raises(Semian::TimeoutError) do Semian[:testing].acquire {} end end end assert_notify(:circuit_open) do - assert_raises Semian::OpenCircuitError do + assert_raises(Semian::OpenCircuitError) do Semian[:testing].acquire {} end end @@ -59,7 +61,7 @@ def test_success_instrumentation_wait_time def test_success_instrumentation_when_unknown_exceptions_occur assert_notify(:success) do - assert_raises RuntimeError do + assert_raises(RuntimeError) do Semian[:testing].acquire { raise "Some error" } end end @@ -73,7 +75,7 @@ def assert_notify(*expected_events) events << event end yield - assert_equal expected_events, events, "The timeline of events was not as expected" + assert_equal(expected_events, events, "The timeline of events was not as expected") ensure Semian.unsubscribe(subscription) end diff --git a/test/lru_hash_test.rb b/test/lru_hash_test.rb index 8e0d10b6d..97567ddfc 100644 --- a/test/lru_hash_test.rb +++ b/test/lru_hash_test.rb @@ -1,4 +1,6 @@ -require 'test_helper' +# frozen_string_literal: true + +require "test_helper" class TestLRUHash < Minitest::Test def setup @@ -7,128 +9,129 @@ def setup end def test_set_get_item - circuit_breaker = create_circuit_breaker('a') - @lru_hash.set('key', circuit_breaker) - assert_equal circuit_breaker, @lru_hash.get('key') + circuit_breaker = create_circuit_breaker("a") + @lru_hash.set("key", circuit_breaker) + assert_equal(circuit_breaker, @lru_hash.get("key")) end def test_set_get_item_with_thread_safe_disabled Semian.thread_safe = false @lru_hash = LRUHash.new - circuit_breaker = create_circuit_breaker('a') - @lru_hash.set('key', circuit_breaker) - assert_equal circuit_breaker, @lru_hash.get('key') + circuit_breaker = create_circuit_breaker("a") + @lru_hash.set("key", circuit_breaker) + assert_equal(circuit_breaker, @lru_hash.get("key")) end def test_set_get_item_with_thread_safe_enabled - circuit_breaker = create_circuit_breaker('a') - @lru_hash.set('key', circuit_breaker) - assert_equal circuit_breaker, @lru_hash.get('key') + circuit_breaker = create_circuit_breaker("a") + @lru_hash.set("key", circuit_breaker) + assert_equal(circuit_breaker, @lru_hash.get("key")) end def test_remove_item - @lru_hash.set('a', create_circuit_breaker('a')) - @lru_hash.set('b', create_circuit_breaker('b')) - @lru_hash.set('c', create_circuit_breaker('c')) + @lru_hash.set("a", create_circuit_breaker("a")) + @lru_hash.set("b", create_circuit_breaker("b")) + @lru_hash.set("c", create_circuit_breaker("c")) - assert_equal 3, @lru_hash.count + assert_equal(3, @lru_hash.count) - @lru_hash.delete('b') - assert_equal 2, @lru_hash.size - assert_equal @lru_hash.values.last, @lru_hash.get('c') - assert_equal @lru_hash.values.first, @lru_hash.get('a') + @lru_hash.delete("b") + assert_equal(2, @lru_hash.size) + assert_equal(@lru_hash.values.last, @lru_hash.get("c")) + assert_equal(@lru_hash.values.first, @lru_hash.get("a")) end def test_get_moves_the_item_at_the_top - @lru_hash.set('a', create_circuit_breaker('a')) - @lru_hash.set('b', create_circuit_breaker('b')) - @lru_hash.set('c', create_circuit_breaker('c')) + @lru_hash.set("a", create_circuit_breaker("a")) + @lru_hash.set("b", create_circuit_breaker("b")) + @lru_hash.set("c", create_circuit_breaker("c")) - assert_equal 3, @lru_hash.size - @lru_hash.get('a') # Reading the value will move the resource at the tail position - assert_equal @lru_hash.values.last, @lru_hash.get('a') - assert_equal @lru_hash.values.first, @lru_hash.get('b') + assert_equal(3, @lru_hash.size) + @lru_hash.get("a") # Reading the value will move the resource at the tail position + assert_equal(@lru_hash.values.last, @lru_hash.get("a")) + assert_equal(@lru_hash.values.first, @lru_hash.get("b")) end def test_set_cleans_resources_if_last_error_has_expired - @lru_hash.set('b', create_circuit_breaker('b', true, false, 1000)) + @lru_hash.set("b", create_circuit_breaker("b", true, false, 1000)) Timecop.travel(2000) do - @lru_hash.set('d', create_circuit_breaker('d')) - assert_equal 1, @lru_hash.size + @lru_hash.set("d", create_circuit_breaker("d")) + assert_equal(1, @lru_hash.size) end end def test_set_does_not_clean_resources_if_last_error_has_not_expired - @lru_hash.set('b', create_circuit_breaker('b', true, false, 1000)) + @lru_hash.set("b", create_circuit_breaker("b", true, false, 1000)) Timecop.travel(600) do - @lru_hash.set('d', create_circuit_breaker('d')) - assert_equal 2, @lru_hash.size + @lru_hash.set("d", create_circuit_breaker("d")) + assert_equal(2, @lru_hash.size) end end def test_set_cleans_resources_if_minimum_time_is_reached - @lru_hash.set('a', create_circuit_breaker('a', true, false, 1000)) - @lru_hash.set('b', create_circuit_breaker('b', false)) - @lru_hash.set('c', create_circuit_breaker('c', false)) + @lru_hash.set("a", create_circuit_breaker("a", true, false, 1000)) + @lru_hash.set("b", create_circuit_breaker("b", false)) + @lru_hash.set("c", create_circuit_breaker("c", false)) Timecop.travel(600) do - @lru_hash.set('d', create_circuit_breaker('d')) - assert_equal 2, @lru_hash.size + @lru_hash.set("d", create_circuit_breaker("d")) + assert_equal(2, @lru_hash.size) end end def test_set_does_not_cleans_resources_if_minimum_time_is_not_reached - @lru_hash.set('a', create_circuit_breaker('a')) - @lru_hash.set('b', create_circuit_breaker('b', false)) - @lru_hash.set('c', create_circuit_breaker('c')) + @lru_hash.set("a", create_circuit_breaker("a")) + @lru_hash.set("b", create_circuit_breaker("b", false)) + @lru_hash.set("c", create_circuit_breaker("c")) - assert_equal 3, @lru_hash.size + assert_equal(3, @lru_hash.size) end def test_keys - @lru_hash.set('a', create_circuit_breaker('a')) - assert_equal ['a'], @lru_hash.keys + @lru_hash.set("a", create_circuit_breaker("a")) + assert_equal(["a"], @lru_hash.keys) end def test_delete - @lru_hash.set('a', create_circuit_breaker('a')) - assert @lru_hash.get('a') + @lru_hash.set("a", create_circuit_breaker("a")) + assert(@lru_hash.get("a")) - @lru_hash.delete('a') - assert_nil @lru_hash.get('a') + @lru_hash.delete("a") + assert_nil(@lru_hash.get("a")) end def test_clear - @lru_hash.set('a', create_circuit_breaker('a')) + @lru_hash.set("a", create_circuit_breaker("a")) @lru_hash.clear - assert @lru_hash.empty? + assert_empty(@lru_hash) end def test_clean_instrumentation - @lru_hash.set('a', create_circuit_breaker('a', true, false, 1000)) - @lru_hash.set('b', create_circuit_breaker('b', true, false, 1000)) - @lru_hash.set('c', create_circuit_breaker('c', true, false, 1000)) + @lru_hash.set("a", create_circuit_breaker("a", true, false, 1000)) + @lru_hash.set("b", create_circuit_breaker("b", true, false, 1000)) + @lru_hash.set("c", create_circuit_breaker("c", true, false, 1000)) notified = false subscriber = Semian.subscribe do |event, resource, scope, adapter, payload| next unless event == :lru_hash_gc + notified = true - assert_equal @lru_hash, resource - assert_nil scope - assert_nil adapter - assert_equal 4, payload[:size] - assert_equal 4, payload[:examined] - assert_equal 3, payload[:cleared] - refute_nil payload[:elapsed] + assert_equal(@lru_hash, resource) + assert_nil(scope) + assert_nil(adapter) + assert_equal(4, payload[:size]) + assert_equal(4, payload[:examined]) + assert_equal(3, payload[:cleared]) + refute_nil(payload[:elapsed]) end Timecop.travel(2000) do - @lru_hash.set('d', create_circuit_breaker('d')) + @lru_hash.set("d", create_circuit_breaker("d")) end - assert notified, 'No notifications has been emitted' + assert(notified, "No notifications has been emitted") ensure Semian.unsubscribe(subscriber) end @@ -142,46 +145,46 @@ def test_monotonically_increasing notification += 1 if notification < 5 - assert_equal notification, payload[:size] - assert_equal 1, payload[:examined] + assert_equal(notification, payload[:size]) + assert_equal(1, payload[:examined]) elsif notification == 5 # At this point, the table looks like: [a, c, b, d, e] - assert_equal notification, payload[:size] - assert_equal 3, payload[:examined] + assert_equal(notification, payload[:size]) + assert_equal(3, payload[:examined]) else - assert_nil true + assert_nil(true) end end assert_monotonic = lambda do previous_timestamp = start_time @lru_hash.keys.zip(@lru_hash.values).each do |key, val| - assert val.updated_at > previous_timestamp, "Timestamp for #{key} was not monotonically increasing" + assert(val.updated_at > previous_timestamp, "Timestamp for #{key} was not monotonically increasing") end end - @lru_hash.set('a', create_circuit_breaker('a')) - @lru_hash.set('b', create_circuit_breaker('b')) - @lru_hash.set('c', create_circuit_breaker('c')) + @lru_hash.set("a", create_circuit_breaker("a")) + @lru_hash.set("b", create_circuit_breaker("b")) + @lru_hash.set("c", create_circuit_breaker("c")) # Before: [a, b, c] # After: [a, c, b] Timecop.travel(Semian.minimum_lru_time - 1) do - @lru_hash.get('b') + @lru_hash.get("b") assert_monotonic.call end # Before: [a, c, b] # After: [a, c, b, d] Timecop.travel(Semian.minimum_lru_time - 1) do - @lru_hash.set('d', create_circuit_breaker('d')) + @lru_hash.set("d", create_circuit_breaker("d")) assert_monotonic.call end # Before: [a, c, b, d] # After: [b, d, e] Timecop.travel(Semian.minimum_lru_time) do - @lru_hash.set('e', create_circuit_breaker('e')) + @lru_hash.set("e", create_circuit_breaker("e")) assert_monotonic.call end ensure @@ -190,35 +193,35 @@ def test_monotonically_increasing def test_max_size lru_hash = LRUHash.new(max_size: 3) - lru_hash.set('a', create_circuit_breaker('a')) - lru_hash.set('b', create_circuit_breaker('b')) - lru_hash.set('c', create_circuit_breaker('c')) - assert_equal 3, lru_hash.size + lru_hash.set("a", create_circuit_breaker("a")) + lru_hash.set("b", create_circuit_breaker("b")) + lru_hash.set("c", create_circuit_breaker("c")) + assert_equal(3, lru_hash.size) Timecop.travel(Semian.minimum_lru_time) do # [a, b, c] are older than the min_time, so they get garbage collected. - lru_hash.set('d', create_circuit_breaker('d')) - assert_equal 1, lru_hash.size + lru_hash.set("d", create_circuit_breaker("d")) + assert_equal(1, lru_hash.size) end end def test_max_size_overflow lru_hash = LRUHash.new(max_size: 3) - lru_hash.set('a', create_circuit_breaker('a')) - lru_hash.set('b', create_circuit_breaker('b')) - assert_equal 2, lru_hash.size + lru_hash.set("a", create_circuit_breaker("a")) + lru_hash.set("b", create_circuit_breaker("b")) + assert_equal(2, lru_hash.size) Timecop.travel(Semian.minimum_lru_time) do # [a, b] are older than the min_time, but the hash isn't full, so # there's no garbage collection. - lru_hash.set('c', create_circuit_breaker('c')) - assert_equal 3, lru_hash.size + lru_hash.set("c", create_circuit_breaker("c")) + assert_equal(3, lru_hash.size) end Timecop.travel(Semian.minimum_lru_time + 1) do # [a, b] are beyond the min_time, but [c] isn't. - lru_hash.set('d', create_circuit_breaker('d')) - assert_equal 2, lru_hash.size + lru_hash.set("d", create_circuit_breaker("d")) + assert_equal(2, lru_hash.size) end end @@ -241,6 +244,7 @@ def create_circuit_breaker(name, exceptions = true, bulkhead = false, error_time def create_bulkhead(name, bulkhead) return nil unless bulkhead + Semian::Resource.new(name, tickets: 1, quota: nil, permissions: 0660, timeout: 0) end end diff --git a/test/mysql2_test.rb b/test/mysql2_test.rb index 1f55be105..7af4440ca 100644 --- a/test/mysql2_test.rb +++ b/test/mysql2_test.rb @@ -1,4 +1,6 @@ -require 'test_helper' +# frozen_string_literal: true + +require "test_helper" class TestMysql2 < Minitest::Test ERROR_TIMEOUT = 5 @@ -18,30 +20,31 @@ def setup end def test_semian_identifier - assert_equal :mysql_foo, FakeMysql.new(semian: {name: 'foo'}).semian_identifier - assert_equal :'mysql_localhost:3306', FakeMysql.new.semian_identifier - assert_equal :'mysql_127.0.0.1:3306', FakeMysql.new(host: '127.0.0.1').semian_identifier - assert_equal :'mysql_example.com:42', FakeMysql.new(host: 'example.com', port: 42).semian_identifier + assert_equal(:mysql_foo, FakeMysql.new(semian: { name: "foo" }).semian_identifier) + assert_equal(:"mysql_localhost:3306", FakeMysql.new.semian_identifier) + assert_equal(:"mysql_127.0.0.1:3306", FakeMysql.new(host: "127.0.0.1").semian_identifier) + assert_equal(:"mysql_example.com:42", FakeMysql.new(host: "example.com", port: 42).semian_identifier) end def test_semian_can_be_disabled resource = Mysql2::Client.new( - host: SemianConfig['toxiproxy_upstream_host'], - port: SemianConfig['mysql_toxiproxy_port'], - semian: false).semian_resource + host: SemianConfig["toxiproxy_upstream_host"], + port: SemianConfig["mysql_toxiproxy_port"], + semian: false + ).semian_resource - assert_instance_of Semian::UnprotectedResource, resource + assert_instance_of(Semian::UnprotectedResource, resource) end def test_connection_errors_open_the_circuit @proxy.downstream(:latency, latency: 2200).apply do ERROR_THRESHOLD.times do - assert_raises ::Mysql2::Error do + assert_raises(::Mysql2::Error) do connect_to_mysql! end end - assert_raises ::Mysql2::CircuitOpenError do + assert_raises(::Mysql2::CircuitOpenError) do connect_to_mysql! end end @@ -50,8 +53,8 @@ def test_connection_errors_open_the_circuit def test_query_errors_does_not_open_the_circuit client = connect_to_mysql! (ERROR_THRESHOLD * 2).times do - assert_raises ::Mysql2::Error do - client.query('ERROR!') + assert_raises(::Mysql2::Error) do + client.query("ERROR!") end end end @@ -59,16 +62,17 @@ def test_query_errors_does_not_open_the_circuit def test_read_timeout_error_open_the_circuit client = connect_to_mysql! - (ERROR_THRESHOLD).times do - assert_raises Mysql2::Error do + ERROR_THRESHOLD.times do + assert_raises(Mysql2::Error) do # Should raise an exception like - - # Mysql2::Error Exception: [mysql_testing] Timeout waiting for a response from the last query. (waited 2 seconds) - client.query('SELECT sleep(5)') + # Mysql2::Error Exception: [mysql_testing] Timeout waiting for a response + # from the last query. (waited 2 seconds) + client.query("SELECT sleep(5)") end end - assert_raises Mysql2::CircuitOpenError do - client.query('SELECT sleep(5)') + assert_raises(Mysql2::CircuitOpenError) do + client.query("SELECT sleep(5)") end # After Mysql2::CircuitOpenError check regular queries are working fine. @@ -78,7 +82,7 @@ def test_read_timeout_error_open_the_circuit result = client.query("SELECT #{query_string};") end - assert_equal 2, result.first[query_string] + assert_equal(2, result.first[query_string]) end def test_connect_instrumentation @@ -87,14 +91,14 @@ def test_connect_instrumentation next unless event == :success notified = true - assert_equal Semian[:mysql_testing], resource - assert_equal :connection, scope - assert_equal :mysql, adapter + assert_equal(Semian[:mysql_testing], resource) + assert_equal(:connection, scope) + assert_equal(:mysql, adapter) end connect_to_mysql! - assert notified, 'No notifications has been emitted' + assert(notified, "No notifications has been emitted") ensure Semian.unsubscribe(subscriber) end @@ -103,37 +107,37 @@ def test_resource_acquisition_for_connect connect_to_mysql! Semian[:mysql_testing].acquire do - error = assert_raises Mysql2::ResourceBusyError do + error = assert_raises(Mysql2::ResourceBusyError) do connect_to_mysql! end - assert_equal :mysql_testing, error.semian_identifier + assert_equal(:mysql_testing, error.semian_identifier) end end def test_network_errors_are_tagged_with_the_resource_identifier client = connect_to_mysql! @proxy.down do - error = assert_raises ::Mysql2::Error do - client.query('SELECT 1 + 1;') + error = assert_raises(::Mysql2::Error) do + client.query("SELECT 1 + 1;") end - assert_equal client.semian_identifier, error.semian_identifier + assert_equal(client.semian_identifier, error.semian_identifier) end end def test_other_mysql_errors_are_not_tagged_with_the_resource_identifier client = connect_to_mysql! - error = assert_raises Mysql2::Error do - client.query('SYNTAX ERROR!') + error = assert_raises(Mysql2::Error) do + client.query("SYNTAX ERROR!") end - assert_nil error.semian_identifier + assert_nil(error.semian_identifier) end def test_resource_timeout_on_connect @proxy.downstream(:latency, latency: 500).apply do background { connect_to_mysql! } - assert_raises Mysql2::ResourceBusyError do + assert_raises(Mysql2::ResourceBusyError) do connect_to_mysql! end end @@ -144,7 +148,7 @@ def test_circuit_breaker_on_connect background { connect_to_mysql! } ERROR_THRESHOLD.times do - assert_raises Mysql2::ResourceBusyError do + assert_raises(Mysql2::ResourceBusyError) do connect_to_mysql! end end @@ -152,7 +156,7 @@ def test_circuit_breaker_on_connect yield_to_background - assert_raises Mysql2::CircuitOpenError do + assert_raises(Mysql2::CircuitOpenError) do connect_to_mysql! end @@ -167,15 +171,15 @@ def test_query_instrumentation notified = false subscriber = Semian.subscribe do |event, resource, scope, adapter| notified = true - assert_equal :success, event - assert_equal Semian[:mysql_testing], resource - assert_equal :query, scope - assert_equal :mysql, adapter + assert_equal(:success, event) + assert_equal(Semian[:mysql_testing], resource) + assert_equal(:query, scope) + assert_equal(:mysql, adapter) end - client.query('SELECT 1 + 1;') + client.query("SELECT 1 + 1;") - assert notified, 'No notifications has been emitted' + assert(notified, "No notifications has been emitted") ensure Semian.unsubscribe(subscriber) end @@ -184,8 +188,8 @@ def test_resource_acquisition_for_query client = connect_to_mysql! Semian[:mysql_testing].acquire do - assert_raises Mysql2::ResourceBusyError do - client.query('SELECT 1 + 1;') + assert_raises(Mysql2::ResourceBusyError) do + client.query("SELECT 1 + 1;") end end end @@ -193,63 +197,63 @@ def test_resource_acquisition_for_query def test_semian_allows_rollback client = connect_to_mysql! - client.query('START TRANSACTION;') + client.query("START TRANSACTION;") Semian[:mysql_testing].acquire do - client.query('ROLLBACK;') + client.query("ROLLBACK;") end end def test_semian_allows_rollback_with_marginalia client = connect_to_mysql! - client.query('START TRANSACTION;') + client.query("START TRANSACTION;") Semian[:mysql_testing].acquire do - client.query('/*foo:bar*/ ROLLBACK;') + client.query("/*foo:bar*/ ROLLBACK;") end end def test_semian_allows_commit client = connect_to_mysql! - client.query('START TRANSACTION;') + client.query("START TRANSACTION;") Semian[:mysql_testing].acquire do - client.query('COMMIT;') + client.query("COMMIT;") end end def test_query_whitelisted_returns_false_for_binary_sql - binary_query = File.read(File.expand_path('../fixtures/binary.sql', __FILE__)) + binary_query = File.read(File.expand_path("../fixtures/binary.sql", __FILE__)) client = connect_to_mysql! - refute client.send(:query_whitelisted?, binary_query) + refute(client.send(:query_whitelisted?, binary_query)) end def test_semian_allows_rollback_to_safepoint client = connect_to_mysql! - client.query('START TRANSACTION;') - client.query('SAVEPOINT foobar;') + client.query("START TRANSACTION;") + client.query("SAVEPOINT foobar;") Semian[:mysql_testing].acquire do - client.query('ROLLBACK TO foobar;') + client.query("ROLLBACK TO foobar;") end - client.query('ROLLBACK;') + client.query("ROLLBACK;") end def test_semian_allows_release_savepoint client = connect_to_mysql! - client.query('START TRANSACTION;') - client.query('SAVEPOINT foobar;') + client.query("START TRANSACTION;") + client.query("SAVEPOINT foobar;") Semian[:mysql_testing].acquire do - client.query('RELEASE SAVEPOINT foobar;') + client.query("RELEASE SAVEPOINT foobar;") end - client.query('ROLLBACK;') + client.query("ROLLBACK;") end def test_resource_timeout_on_query @@ -257,10 +261,10 @@ def test_resource_timeout_on_query client2 = connect_to_mysql! @proxy.downstream(:latency, latency: 500).apply do - background { client2.query('SELECT 1 + 1;') } + background { client2.query("SELECT 1 + 1;") } - assert_raises Mysql2::ResourceBusyError do - client.query('SELECT 1 + 1;') + assert_raises(Mysql2::ResourceBusyError) do + client.query("SELECT 1 + 1;") end end end @@ -270,33 +274,33 @@ def test_circuit_breaker_on_query client2 = connect_to_mysql! @proxy.downstream(:latency, latency: 2200).apply do - background { client2.query('SELECT 1 + 1;') } + background { client2.query("SELECT 1 + 1;") } ERROR_THRESHOLD.times do - assert_raises Mysql2::ResourceBusyError do - client.query('SELECT 1 + 1;') + assert_raises(Mysql2::ResourceBusyError) do + client.query("SELECT 1 + 1;") end end end yield_to_background - assert_raises Mysql2::CircuitOpenError do - client.query('SELECT 1 + 1;') + assert_raises(Mysql2::CircuitOpenError) do + client.query("SELECT 1 + 1;") end Timecop.travel(ERROR_TIMEOUT + 1) do - assert_equal 2, client.query('SELECT 1 + 1 as sum;').to_a.first['sum'] + assert_equal(2, client.query("SELECT 1 + 1 as sum;").to_a.first["sum"]) end end def test_unconfigured client = Mysql2::Client.new( - host: SemianConfig['toxiproxy_upstream_host'], - port: SemianConfig['mysql_toxiproxy_port'], + host: SemianConfig["toxiproxy_upstream_host"], + port: SemianConfig["mysql_toxiproxy_port"], ) - assert_equal 2, client.query('SELECT 1 + 1 as sum;').to_a.first['sum'] + assert_equal(2, client.query("SELECT 1 + 1 as sum;").to_a.first["sum"]) end def test_pings_are_circuit_broken @@ -313,10 +317,10 @@ def client.raw_ping client.ping end - assert_equal false, client.ping + refute(client.ping) end - assert_equal ERROR_THRESHOLD, client.instance_variable_get(:@real_pings) + assert_equal(ERROR_THRESHOLD, client.instance_variable_get(:@real_pings)) end def test_changes_timeout_when_half_open_and_configured @@ -324,37 +328,37 @@ def test_changes_timeout_when_half_open_and_configured @proxy.downstream(:latency, latency: 3000).apply do (ERROR_THRESHOLD * 2).times do - assert_raises Mysql2::Error do - client.query('SELECT 1 + 1;') + assert_raises(Mysql2::Error) do + client.query("SELECT 1 + 1;") end end end - assert_raises Mysql2::CircuitOpenError do - client.query('SELECT 1 + 1;') + assert_raises(Mysql2::CircuitOpenError) do + client.query("SELECT 1 + 1;") end Timecop.travel(ERROR_TIMEOUT + 1) do @proxy.downstream(:latency, latency: 1500).apply do - assert_raises Mysql2::Error do - client.query('SELECT 1 + 1;') + assert_raises(Mysql2::Error) do + client.query("SELECT 1 + 1;") end end end Timecop.travel(ERROR_TIMEOUT * 2 + 1) do - client.query('SELECT 1 + 1;') - client.query('SELECT 1 + 1;') + client.query("SELECT 1 + 1;") + client.query("SELECT 1 + 1;") # Timeout has reset to the normal 2 seconds now that Circuit is closed @proxy.downstream(:latency, latency: 1500).apply do - client.query('SELECT 1 + 1;') + client.query("SELECT 1 + 1;") end end - assert_equal 2, client.query_options[:connect_timeout] - assert_equal 2, client.query_options[:read_timeout] - assert_equal 2, client.query_options[:write_timeout] + assert_equal(2, client.query_options[:connect_timeout]) + assert_equal(2, client.query_options[:read_timeout]) + assert_equal(2, client.query_options[:write_timeout]) end def test_circuit_open_errors_do_not_trigger_the_circuit_breaker @@ -364,7 +368,7 @@ def test_circuit_open_errors_do_not_trigger_the_circuit_breaker assert_raises(Mysql2::Error) do connect_to_mysql! end - assert_equal mysql_connection_error, Semian[:mysql_testing].circuit_breaker.last_error.class + assert_equal(mysql_connection_error, Semian[:mysql_testing].circuit_breaker.last_error.class) end end end @@ -377,8 +381,8 @@ def connect_to_mysql!(semian_options = {}) read_timeout: 2, write_timeout: 2, reconnect: true, - host: SemianConfig['toxiproxy_upstream_host'], - port: SemianConfig['mysql_toxiproxy_port'], + host: SemianConfig["toxiproxy_upstream_host"], + port: SemianConfig["mysql_toxiproxy_port"], semian: SEMIAN_OPTIONS.merge(semian_options), ) end diff --git a/test/net_http_test.rb b/test/net_http_test.rb index 9e64baf2f..493980f1a 100644 --- a/test/net_http_test.rb +++ b/test/net_http_test.rb @@ -1,5 +1,7 @@ -require 'test_helper' -require 'semian/net_http' +# frozen_string_literal: true + +require "test_helper" +require "semian/net_http" class TestNetHTTP < Minitest::Test DEFAULT_SEMIAN_OPTIONS = { @@ -9,22 +11,27 @@ class TestNetHTTP < Minitest::Test error_timeout: 10, }.freeze DEFAULT_SEMIAN_CONFIGURATION = proc do |host, port| - next nil if host == SemianConfig['toxiproxy_upstream_host'] && port == SemianConfig['toxiproxy_upstream_port'] # disable if toxiproxy + if host == SemianConfig["toxiproxy_upstream_host"] && \ + port == SemianConfig["toxiproxy_upstream_port"] # disable if toxiproxy + next nil + end + DEFAULT_SEMIAN_OPTIONS.merge(name: "#{host}_#{port}") end def test_semian_identifier with_server do with_semian_configuration do - Net::HTTP.start(SemianConfig['toxiproxy_upstream_host'], SemianConfig['http_toxiproxy_port']) do |http| - assert_equal "nethttp_#{SemianConfig['toxiproxy_upstream_host']}_#{SemianConfig['http_toxiproxy_port']}", http.semian_identifier + Net::HTTP.start(SemianConfig["toxiproxy_upstream_host"], SemianConfig["http_toxiproxy_port"]) do |http| + assert_equal("nethttp_#{SemianConfig["toxiproxy_upstream_host"]}_#{SemianConfig["http_toxiproxy_port"]}", + http.semian_identifier) end end end end def test_changes_timeout_when_half_open_and_configured - http = Net::HTTP.new(SemianConfig['toxiproxy_upstream_host'], SemianConfig['http_toxiproxy_port']) + http = Net::HTTP.new(SemianConfig["toxiproxy_upstream_host"], SemianConfig["http_toxiproxy_port"]) expected_read_timeout = http.read_timeout expected_open_timeout = http.open_timeout options = proc do |host, port| @@ -41,22 +48,22 @@ def test_changes_timeout_when_half_open_and_configured with_semian_configuration(options) do with_server do - Toxiproxy['semian_test_net_http'].downstream(:latency, latency: 2000).apply do - http.get('/200') + Toxiproxy["semian_test_net_http"].downstream(:latency, latency: 2000).apply do + http.get("/200") end half_open_cicuit! - Toxiproxy['semian_test_net_http'].downstream(:latency, latency: 2000).apply do - assert_raises Net::ReadTimeout do - http.get('/200') + Toxiproxy["semian_test_net_http"].downstream(:latency, latency: 2000).apply do + assert_raises(Net::ReadTimeout) do + http.get("/200") end end end end - assert_equal expected_read_timeout, http.read_timeout - assert_equal expected_open_timeout, http.open_timeout + assert_equal(expected_read_timeout, http.read_timeout) + assert_equal(expected_open_timeout, http.open_timeout) end def test_trigger_open @@ -64,11 +71,11 @@ def test_trigger_open with_server do open_circuit! - uri = URI("http://#{SemianConfig['toxiproxy_upstream_host']}:#{SemianConfig['http_toxiproxy_port']}/200") - exception = assert_raises Net::CircuitOpenError do + uri = URI("http://#{SemianConfig["toxiproxy_upstream_host"]}:#{SemianConfig["http_toxiproxy_port"]}/200") + exception = assert_raises(Net::CircuitOpenError) do Net::HTTP.get(uri) end - assert_equal "Net::ReadTimeout", exception.cause.to_s + assert_equal("Net::ReadTimeout", exception.cause.to_s) end end end @@ -94,12 +101,12 @@ def test_bulkheads_tickets_are_working end with_semian_configuration(options) do with_server do - http_1 = Net::HTTP.new(SemianConfig['toxiproxy_upstream_host'], SemianConfig['http_toxiproxy_port']) + http_1 = Net::HTTP.new(SemianConfig["toxiproxy_upstream_host"], SemianConfig["http_toxiproxy_port"]) http_1.semian_resource.acquire do - http_2 = Net::HTTP.new(SemianConfig['toxiproxy_upstream_host'], SemianConfig['http_toxiproxy_port']) + http_2 = Net::HTTP.new(SemianConfig["toxiproxy_upstream_host"], SemianConfig["http_toxiproxy_port"]) http_2.semian_resource.acquire do - assert_raises Net::ResourceBusyError do - Net::HTTP.get(URI("http://#{SemianConfig['toxiproxy_upstream_host']}:#{SemianConfig['http_toxiproxy_port']}/")) + assert_raises(Net::ResourceBusyError) do + Net::HTTP.get(URI("http://#{SemianConfig["toxiproxy_upstream_host"]}:#{SemianConfig["http_toxiproxy_port"]}/")) end end end @@ -111,8 +118,8 @@ def test_get_is_protected with_semian_configuration do with_server do open_circuit! - assert_raises Net::CircuitOpenError do - Net::HTTP.get(URI("http://#{SemianConfig['toxiproxy_upstream_host']}:#{SemianConfig['http_toxiproxy_port']}/200")) + assert_raises(Net::CircuitOpenError) do + Net::HTTP.get(URI("http://#{SemianConfig["toxiproxy_upstream_host"]}:#{SemianConfig["http_toxiproxy_port"]}/200")) end end end @@ -123,8 +130,8 @@ def test_instance_get_is_protected with_server do open_circuit! - assert_raises Net::CircuitOpenError do - http = Net::HTTP.new(SemianConfig['toxiproxy_upstream_host'], SemianConfig['http_toxiproxy_port']) + assert_raises(Net::CircuitOpenError) do + http = Net::HTTP.new(SemianConfig["toxiproxy_upstream_host"], SemianConfig["http_toxiproxy_port"]) http.get("/") end end @@ -136,8 +143,8 @@ def test_get_response_is_protected with_server do open_circuit! - assert_raises Net::CircuitOpenError do - uri = URI("http://#{SemianConfig['toxiproxy_upstream_host']}:#{SemianConfig['http_toxiproxy_port']}/200") + assert_raises(Net::CircuitOpenError) do + uri = URI("http://#{SemianConfig["toxiproxy_upstream_host"]}:#{SemianConfig["http_toxiproxy_port"]}/200") Net::HTTP.get_response(uri) end end @@ -149,9 +156,9 @@ def test_post_form_is_protected with_server do open_circuit! - assert_raises Net::CircuitOpenError do - uri = URI("http://#{SemianConfig['toxiproxy_upstream_host']}:#{SemianConfig['http_toxiproxy_port']}/200") - Net::HTTP.post_form(uri, 'q' => 'ruby', 'max' => '50') + assert_raises(Net::CircuitOpenError) do + uri = URI("http://#{SemianConfig["toxiproxy_upstream_host"]}:#{SemianConfig["http_toxiproxy_port"]}/200") + Net::HTTP.post_form(uri, "q" => "ruby", "max" => "50") end end end @@ -162,8 +169,8 @@ def test_http_start_method_is_protected with_server do open_circuit! - uri = URI("http://#{SemianConfig['toxiproxy_upstream_host']}:#{SemianConfig['http_toxiproxy_port']}/200") - assert_raises Net::CircuitOpenError do + uri = URI("http://#{SemianConfig["toxiproxy_upstream_host"]}:#{SemianConfig["http_toxiproxy_port"]}/200") + assert_raises(Net::CircuitOpenError) do Net::HTTP.start(uri.host, uri.port) {} end close_circuit! @@ -174,12 +181,12 @@ def test_http_start_method_is_protected def test_http_action_request_inside_start_methods_are_protected with_semian_configuration do with_server do - uri = URI("http://#{SemianConfig['toxiproxy_upstream_host']}:#{SemianConfig['http_toxiproxy_port']}/200") + uri = URI("http://#{SemianConfig["toxiproxy_upstream_host"]}:#{SemianConfig["http_toxiproxy_port"]}/200") Net::HTTP.start(uri.host, uri.port) do |http| open_circuit! get_subclasses(Net::HTTPRequest).each do |action| assert_raises(Net::CircuitOpenError, "#{action.name} did not raise a Net::CircuitOpenError") do - request = action.new uri + request = action.new(uri) http.request(request) end end @@ -190,8 +197,8 @@ def test_http_action_request_inside_start_methods_are_protected def test_custom_raw_semian_options_work_with_lookup with_server do - toxiproxy_upstream_host = SemianConfig['toxiproxy_upstream_host'] - http_toxiproxy_port = SemianConfig['http_toxiproxy_port'] + toxiproxy_upstream_host = SemianConfig["toxiproxy_upstream_host"] + http_toxiproxy_port = SemianConfig["http_toxiproxy_port"] semian_config = {} semian_config["development"] = {} @@ -205,8 +212,8 @@ def test_custom_raw_semian_options_work_with_lookup with_semian_configuration(semian_configuration_proc) do Net::HTTP.start(toxiproxy_upstream_host, http_toxiproxy_port) do |http| - assert_equal semian_config["development"][http.semian_identifier], - http.raw_semian_options.dup.tap { |o| o.delete(:name) } + assert_equal(semian_config["development"][http.semian_identifier], + http.raw_semian_options.dup.tap { |o| o.delete(:name) }) end end end @@ -235,12 +242,12 @@ def test_custom_raw_semian_options_work_with_default_fallback semian_identifier = "nethttp_default" unless semian_config[sample_env].key?(semian_identifier) semian_config[sample_env][semian_identifier].merge(name: "default") end - Semian["nethttp_default"].reset if Semian["nethttp_default"] + Semian["nethttp_default"]&.reset Semian.destroy("nethttp_default") with_semian_configuration(semian_configuration_proc) do - Net::HTTP.start(SemianConfig['http_host'], SemianConfig['http_port_service_a']) do |http| + Net::HTTP.start(SemianConfig["http_host"], SemianConfig["http_port_service_a"]) do |http| expected_config = semian_config["development"]["nethttp_default"].dup - assert_equal expected_config, http.raw_semian_options.dup.tap { |o| o.delete(:name) } + assert_equal(expected_config, http.raw_semian_options.dup.tap { |o| o.delete(:name) }) end end end @@ -250,16 +257,17 @@ def test_custom_raw_semian_options_can_disable_using_nil with_server do semian_configuration_proc = proc { nil } with_semian_configuration(semian_configuration_proc) do - http = Net::HTTP.new(SemianConfig['toxiproxy_upstream_host'], SemianConfig['http_toxiproxy_port']) - assert_equal true, http.disabled? + http = Net::HTTP.new(SemianConfig["toxiproxy_upstream_host"], SemianConfig["http_toxiproxy_port"]) + assert_predicate(http, :disabled?) end end end + def test_disable_semian_should_be_false with_server do with_semian_configuration do - http = Net::HTTP.new(SemianConfig['toxiproxy_upstream_host'], SemianConfig['http_toxiproxy_port']) - assert_equal false, http.disabled? + http = Net::HTTP.new(SemianConfig["toxiproxy_upstream_host"], SemianConfig["http_toxiproxy_port"]) + refute_predicate(http, :disabled?) end end end @@ -267,8 +275,9 @@ def test_disable_semian_should_be_false def test_disable_semian_for_all_http_requests_with_flag with_server do with_semian_configuration do - http = Net::HTTP.new(SemianConfig['toxiproxy_upstream_host'], SemianConfig['http_toxiproxy_port'], semian: false) - assert_equal true, http.disabled? + http = Net::HTTP.new(SemianConfig["toxiproxy_upstream_host"], SemianConfig["http_toxiproxy_port"], + semian: false) + assert_predicate(http, :disabled?) end end end @@ -280,20 +289,25 @@ def test_use_custom_configuration_to_combine_endpoints_into_one_resource sample_env = "development" semian_configuration_proc = proc do |host, port| - next nil if host == SemianConfig['toxiproxy_upstream_host'] && port == SemianConfig['toxiproxy_upstream_port'] # disable if toxiproxy + if host == SemianConfig["toxiproxy_upstream_host"] && \ + port == SemianConfig["toxiproxy_upstream_port"] # disable if toxiproxy + next nil + end + semian_identifier = "nethttp_default" semian_config[sample_env][semian_identifier].merge(name: "default") end with_semian_configuration(semian_configuration_proc) do - Semian["nethttp_default"].reset if Semian["nethttp_default"] + Semian["nethttp_default"]&.reset Semian.destroy("nethttp_default") with_server do open_circuit! end - with_server(ports: [SemianConfig['http_port_service_a'], SemianConfig['http_port_service_b']], reset_semian_state: false) do - assert_raises Net::CircuitOpenError do - Net::HTTP.get(URI("http://#{SemianConfig['toxiproxy_upstream_host']}:#{SemianConfig['http_toxiproxy_port']}/200")) + with_server(ports: [SemianConfig["http_port_service_a"], SemianConfig["http_port_service_b"]], + reset_semian_state: false) do + assert_raises(Net::CircuitOpenError) do + Net::HTTP.get(URI("http://#{SemianConfig["toxiproxy_upstream_host"]}:#{SemianConfig["http_toxiproxy_port"]}/200")) end end end @@ -301,8 +315,8 @@ def test_use_custom_configuration_to_combine_endpoints_into_one_resource def test_custom_raw_semian_options_can_disable_with_invalid_key with_server do - toxiproxy_upstream_host = SemianConfig['toxiproxy_upstream_host'] - http_toxiproxy_port = SemianConfig['http_toxiproxy_port'] + toxiproxy_upstream_host = SemianConfig["toxiproxy_upstream_host"] + http_toxiproxy_port = SemianConfig["http_toxiproxy_port"] semian_config = {} semian_config["development"] = {} @@ -315,10 +329,10 @@ def test_custom_raw_semian_options_can_disable_with_invalid_key end with_semian_configuration(semian_configuration_proc) do http = Net::HTTP.new(toxiproxy_upstream_host, http_toxiproxy_port) - assert_equal false, http.disabled? + refute_predicate(http, :disabled?) http = Net::HTTP.new(toxiproxy_upstream_host, http_toxiproxy_port + 100) - assert_equal true, http.disabled? + assert_predicate(http, :disabled?) end end end @@ -337,14 +351,16 @@ def test_adding_custom_errors_do_trip_circuit with_semian_configuration do with_custom_errors([::OpenSSL::SSL::SSLError]) do with_server do - http = Net::HTTP.new(SemianConfig['toxiproxy_upstream_host'], SemianConfig['http_toxiproxy_port']) + http = Net::HTTP.new(SemianConfig["toxiproxy_upstream_host"], SemianConfig["http_toxiproxy_port"]) http.use_ssl = true http.raw_semian_options[:error_threshold].times do - assert_raises ::OpenSSL::SSL::SSLError, "for HTTP request to #{SemianConfig['toxiproxy_upstream_host']}:#{SemianConfig['http_toxiproxy_port']}" do + assert_msg = "for HTTP request to #{SemianConfig["toxiproxy_upstream_host"]}:" \ + "#{SemianConfig["http_toxiproxy_port"]}" + assert_raises(::OpenSSL::SSL::SSLError, assert_msg) do http.get("/200") end end - assert_raises Net::CircuitOpenError do + assert_raises(Net::CircuitOpenError) do http.get("/200") end end @@ -366,11 +382,11 @@ def test_5xxs_trip_circuit_when_fatal_server_flag_enabled with_semian_configuration(options) do with_server do - http = Net::HTTP.new(SemianConfig['http_host'], SemianConfig['http_port_service_a']) + http = Net::HTTP.new(SemianConfig["http_host"], SemianConfig["http_port_service_a"]) http.raw_semian_options[:error_threshold].times do http.get("/500") end - assert_raises Net::CircuitOpenError do + assert_raises(Net::CircuitOpenError) do http.get("/500") end end @@ -381,7 +397,7 @@ def test_5xxs_dont_raise_exceptions_unless_fatal_server_flag_enabled skip if ENV["SKIP_FLAKY_TESTS"] with_semian_configuration do with_server do - http = Net::HTTP.new(SemianConfig['http_host'], SemianConfig['http_port_service_a']) + http = Net::HTTP.new(SemianConfig["http_host"], SemianConfig["http_port_service_a"]) http.raw_semian_options[:error_threshold].times do http.get("/500") end @@ -392,38 +408,38 @@ def test_5xxs_dont_raise_exceptions_unless_fatal_server_flag_enabled def test_multiple_different_endpoints_and_ports_are_tracked_differently with_semian_configuration do - ports = [SemianConfig['http_port_service_a'], SemianConfig['http_port_service_b']] + ports = [SemianConfig["http_port_service_a"], SemianConfig["http_port_service_b"]] ports.each do |port| reset_semian_resource(port: port.to_i) end with_server(ports: ports, reset_semian_state: false) do |host, port| - with_toxic(hostname: host, upstream_port: SemianConfig['http_port_service_a'], toxic_port: port + 1) do |name| + with_toxic(hostname: host, upstream_port: SemianConfig["http_port_service_a"], toxic_port: port + 1) do |name| Net::HTTP.get(URI("http://#{host}:#{port + 1}/")) open_circuit!(hostname: host, toxic_port: port + 1, toxic_name: name) - assert_raises Net::CircuitOpenError do + assert_raises(Net::CircuitOpenError) do Net::HTTP.get(URI("http://#{host}:#{port + 1}/")) end end end - with_server(ports: [SemianConfig['http_port_service_a']], reset_semian_state: false) do + with_server(ports: [SemianConfig["http_port_service_a"]], reset_semian_state: false) do # different endpoint, should not raise errors even though localhost == 127.0.0.1 - Net::HTTP.get(URI("http://#{SemianConfig['toxiproxy_upstream_host']}:#{SemianConfig['http_toxiproxy_port']}/")) + Net::HTTP.get(URI("http://#{SemianConfig["toxiproxy_upstream_host"]}:#{SemianConfig["http_toxiproxy_port"]}/")) end end end def test_persistent_state_after_server_restart with_semian_configuration do - with_server(ports: [SemianConfig['http_port_service_b']]) do |_, port| - with_toxic(hostname: SemianConfig['http_host'], upstream_port: port, toxic_port: port + 1) do |name| - open_circuit!(hostname: SemianConfig['toxiproxy_upstream_host'], toxic_port: port + 1, toxic_name: name) + with_server(ports: [SemianConfig["http_port_service_b"]]) do |_, port| + with_toxic(hostname: SemianConfig["http_host"], upstream_port: port, toxic_port: port + 1) do |name| + open_circuit!(hostname: SemianConfig["toxiproxy_upstream_host"], toxic_port: port + 1, toxic_name: name) end end - with_server(ports: [SemianConfig['http_port_service_b']], reset_semian_state: false) do |_, port| - with_toxic(hostname: SemianConfig['http_host'], upstream_port: port, toxic_port: port + 1) do |_| - assert_raises Net::CircuitOpenError do - Net::HTTP.get(URI("http://#{SemianConfig['toxiproxy_upstream_host']}:#{port + 1}/200")) + with_server(ports: [SemianConfig["http_port_service_b"]], reset_semian_state: false) do |_, port| + with_toxic(hostname: SemianConfig["http_host"], upstream_port: port, toxic_port: port + 1) do |_| + assert_raises(Net::CircuitOpenError) do + Net::HTTP.get(URI("http://#{SemianConfig["toxiproxy_upstream_host"]}:#{port + 1}/200")) end end end @@ -480,8 +496,8 @@ def get_subclasses(klass) end def open_circuit!(hostname: nil, toxic_port: nil, toxic_name: "semian_test_net_http") - hostname ||= SemianConfig['toxiproxy_upstream_host'] - toxic_port ||= SemianConfig['http_toxiproxy_port'] + hostname ||= SemianConfig["toxiproxy_upstream_host"] + toxic_port ||= SemianConfig["http_toxiproxy_port"] Net::HTTP.start(hostname, toxic_port) do |http| http.read_timeout = 0.1 @@ -490,7 +506,7 @@ def open_circuit!(hostname: nil, toxic_port: nil, toxic_name: "semian_test_net_h # Cause error error_threshold times so circuit opens Toxiproxy[toxic_name].downstream(:latency, latency: 500).apply do request = Net::HTTP::Get.new(uri) - assert_raises Net::ReadTimeout, "for HTTP request to #{uri}" do + assert_raises(Net::ReadTimeout, "for HTTP request to #{uri}") do http.request(request) end end @@ -498,7 +514,7 @@ def open_circuit!(hostname: nil, toxic_port: nil, toxic_name: "semian_test_net_h end end - def close_circuit!(hostname: SemianConfig['toxiproxy_upstream_host'], toxic_port: SemianConfig['http_toxiproxy_port']) + def close_circuit!(hostname: SemianConfig["toxiproxy_upstream_host"], toxic_port: SemianConfig["http_toxiproxy_port"]) http = Net::HTTP.new(hostname, toxic_port) Timecop.travel(http.raw_semian_options[:error_timeout]) # Cause successes success_threshold times so circuit closes @@ -508,7 +524,7 @@ def close_circuit!(hostname: SemianConfig['toxiproxy_upstream_host'], toxic_port end end - def with_server(ports: [SemianConfig['http_port_service_a']], reset_semian_state: true) + def with_server(ports: [SemianConfig["http_port_service_a"]], reset_semian_state: true) ports.each do |port| reset_semian_resource(port: port) if reset_semian_state @proxy = Toxiproxy[:semian_test_net_http] @@ -516,21 +532,25 @@ def with_server(ports: [SemianConfig['http_port_service_a']], reset_semian_state end end - def reset_semian_resource(hostname: SemianConfig['toxiproxy_upstream_host'], port:) - Semian["nethttp_#{hostname}_#{port}"].reset if Semian["nethttp_#{hostname}_#{port}"] - Semian["nethttp_#{hostname}_#{port.to_i + 1}"].reset if Semian["nethttp_#{hostname}_#{port.to_i + 1}"] + def reset_semian_resource(hostname: SemianConfig["toxiproxy_upstream_host"], port:) + Semian["nethttp_#{hostname}_#{port}"]&.reset + Semian["nethttp_#{hostname}_#{port.to_i + 1}"]&.reset Semian.destroy("nethttp_#{hostname}_#{port}") Semian.destroy("nethttp_#{hostname}_#{port.to_i + 1}") end - def with_toxic(hostname: SemianConfig['http_host'], upstream_port: SemianConfig['http_port_service_a'], toxic_port: upstream_port + 1) + def with_toxic( + hostname: SemianConfig["http_host"], + upstream_port: SemianConfig["http_port_service_a"], + toxic_port: upstream_port + 1 + ) old_proxy = @proxy name = "semian_test_net_http_#{hostname}_#{upstream_port}<-#{toxic_port}" Toxiproxy.populate([ { name: name, upstream: "#{hostname}:#{upstream_port}", - listen: "#{SemianConfig['toxiproxy_upstream_host']}:#{toxic_port}", + listen: "#{SemianConfig["toxiproxy_upstream_host"]}:#{toxic_port}", }, ]) @proxy = Toxiproxy[name] diff --git a/test/protected_resource_test.rb b/test/protected_resource_test.rb index c86f32672..2cbb2e5df 100644 --- a/test/protected_resource_test.rb +++ b/test/protected_resource_test.rb @@ -1,5 +1,7 @@ -require 'test_helper' -require 'securerandom' +# frozen_string_literal: true + +require "test_helper" +require "securerandom" class TestProtectedResource < Minitest::Test include CircuitBreakerHelper @@ -32,9 +34,9 @@ def test_acquire_without_bulkhead block_called = false @resource = Semian[:testing] @resource.acquire { block_called = true } - assert_equal true, block_called - assert_instance_of Semian::CircuitBreaker, @resource.circuit_breaker - assert_nil @resource.bulkhead + assert(block_called) + assert_instance_of(Semian::CircuitBreaker, @resource.circuit_breaker) + assert_nil(@resource.bulkhead) end end @@ -52,12 +54,12 @@ def test_acquire_bulkhead_without_circuit_breaker @resource = Semian[:testing] @resource.acquire do acquired = true - assert_equal 1, @resource.count - assert_equal 2, @resource.tickets + assert_equal(1, @resource.count) + assert_equal(2, @resource.tickets) end - assert acquired - assert_nil @resource.circuit_breaker + assert(acquired) + assert_nil(@resource.circuit_breaker) end def test_acquire_bulkhead_with_circuit_breaker @@ -75,17 +77,17 @@ def test_acquire_bulkhead_with_circuit_breaker @resource = Semian[:testing] @resource.acquire do acquired = true - assert_equal 1, @resource.count - assert_equal 2, @resource.tickets + assert_equal(1, @resource.count) + assert_equal(2, @resource.tickets) half_open_cicuit!(@resource) assert_circuit_closed(@resource) end - assert acquired + assert(acquired) end def test_register_without_any_resource_fails - assert_raises ArgumentError do + assert_raises(ArgumentError) do Semian.register( :testing, circuit_breaker: false, @@ -110,8 +112,8 @@ def test_responds_to_name_when_bulkhead_or_circuit_breaker_disabled circuit_breaker: false, ) - assert_equal :no_bulkhead, Semian[:no_bulkhead].name - assert_equal :no_circuit_breaker, Semian[:no_circuit_breaker].name + assert_equal(:no_bulkhead, Semian[:no_bulkhead].name) + assert_equal(:no_circuit_breaker, Semian[:no_circuit_breaker].name) end def test_gracefully_fails_when_unable_to_decrement_ticket_count @@ -148,8 +150,8 @@ def test_gracefully_fails_when_unable_to_decrement_ticket_count Semian.register(name, tickets: 1, **options) ensure - workers.each do |pid| + workers&.each do |pid| Process.kill("INT", pid) - end if workers + end end end diff --git a/test/redis_client_test.rb b/test/redis_client_test.rb index d9f056de2..5b3149b0e 100644 --- a/test/redis_client_test.rb +++ b/test/redis_client_test.rb @@ -1,5 +1,7 @@ -require 'test_helper' -require 'benchmark' +# frozen_string_literal: true + +require "test_helper" +require "benchmark" module RedisClientTests REDIS_TIMEOUT = 0.5 @@ -23,26 +25,28 @@ def setup end def test_semian_identifier - assert_equal :redis_foo, new_config(semian: {name: 'foo'}).semian_identifier - assert_equal :"redis_#{SemianConfig['toxiproxy_upstream_host']}:16379/1", new_config(semian: {name: nil}).semian_identifier - assert_equal :'redis_example.com:42/1', new_config(host: 'example.com', port: 42, semian: {name: nil}).semian_identifier - - config = new_config(semian: {name: 'foo'}) - assert_equal :redis_foo, config.new_client.semian_identifier - assert_equal :redis_foo, config.new_pool.semian_identifier + assert_equal(:redis_foo, new_config(semian: { name: "foo" }).semian_identifier) + assert_equal(:"redis_#{SemianConfig["toxiproxy_upstream_host"]}:16379/1", + new_config(semian: { name: nil }).semian_identifier) + assert_equal(:"redis_example.com:42/1", + new_config(host: "example.com", port: 42, semian: { name: nil }).semian_identifier) + + config = new_config(semian: { name: "foo" }) + assert_equal(:redis_foo, config.new_client.semian_identifier) + assert_equal(:redis_foo, config.new_pool.semian_identifier) end def test_config_alias config = new_config client = config.new_client client2 = config.new_client - assert_equal client.semian_resource, client2.semian_resource - assert_equal client.semian_identifier, client2.semian_identifier + assert_equal(client.semian_resource, client2.semian_resource) + assert_equal(client.semian_identifier, client2.semian_identifier) end def test_semian_can_be_disabled resource = RedisClient.new(semian: false).semian_resource - assert_instance_of Semian::UnprotectedResource, resource + assert_instance_of(Semian::UnprotectedResource, resource) end def test_connection_errors_open_the_circuit @@ -50,13 +54,13 @@ def test_connection_errors_open_the_circuit @proxy.downstream(:latency, latency: 600).apply do ERROR_THRESHOLD.times do - assert_raises RedisClient::ReadTimeoutError do - client.call('GET', 'foo') + assert_raises(RedisClient::ReadTimeoutError) do + client.call("GET", "foo") end end - assert_raises RedisClient::CircuitOpenError do - client.call('GET', 'foo') + assert_raises(RedisClient::CircuitOpenError) do + client.call("GET", "foo") end end end @@ -66,23 +70,23 @@ def test_connection_reset_does_not_open_the_circuit @proxy.downstream(:reset_peer).apply do ERROR_THRESHOLD.times do - assert_raises RedisClient::ConnectionError do - client.call('GET', 'foo') + assert_raises(RedisClient::ConnectionError) do + client.call("GET", "foo") end end - assert_raises RedisClient::ConnectionError do - client.call('GET', 'foo') + assert_raises(RedisClient::ConnectionError) do + client.call("GET", "foo") end end end def test_command_errors_does_not_open_the_circuit client = connect_to_redis! - client.call('HSET', 'my_hash', 'foo', 'bar') + client.call("HSET", "my_hash", "foo", "bar") (ERROR_THRESHOLD * 2).times do - assert_raises RedisClient::CommandError do - client.call('GET', 'my_hash') + assert_raises(RedisClient::CommandError) do + client.call("GET", "my_hash") end end end @@ -94,14 +98,14 @@ def test_connect_instrumentation next if scope == :query notified = true - assert_equal Semian[:redis_testing], resource - assert_equal :connection, scope - assert_equal :redis_client, adapter + assert_equal(Semian[:redis_testing], resource) + assert_equal(:connection, scope) + assert_equal(:redis_client, adapter) end connect_to_redis! - assert notified, 'No notifications has been emitted' + assert(notified, "No notifications has been emitted") ensure Semian.unsubscribe(subscriber) end @@ -110,37 +114,37 @@ def test_resource_acquisition_for_connect connect_to_redis! Semian[:redis_testing].acquire do - error = assert_raises RedisClient::ResourceBusyError do + error = assert_raises(RedisClient::ResourceBusyError) do connect_to_redis! end - assert_equal :redis_testing, error.semian_identifier + assert_equal(:redis_testing, error.semian_identifier) end end def test_redis_connection_errors_are_tagged_with_the_resource_identifier @proxy.downstream(:latency, latency: 600).apply do - error = assert_raises RedisClient::TimeoutError do + error = assert_raises(RedisClient::TimeoutError) do redis = connect_to_redis! - redis.get('foo') + redis.get("foo") end - assert_equal :redis_testing, error.semian_identifier + assert_equal(:redis_testing, error.semian_identifier) end end def test_other_redis_errors_are_not_tagged_with_the_resource_identifier client = connect_to_redis! - client.call('set', 'foo', 'bar') - error = assert_raises RedisClient::CommandError do - client.call('hget', 'foo', 'bar') + client.call("set", "foo", "bar") + error = assert_raises(RedisClient::CommandError) do + client.call("hget", "foo", "bar") end - refute error.respond_to?(:semian_identifier) + refute_respond_to(error, :semian_identifier) end def test_resource_timeout_on_connect @proxy.downstream(:latency, latency: redis_timeout_ms).apply do background { connect_to_redis! } - assert_raises RedisClient::ResourceBusyError do + assert_raises(RedisClient::ResourceBusyError) do connect_to_redis! end end @@ -148,13 +152,13 @@ def test_resource_timeout_on_connect def test_dns_resolution_failures_open_circuit ERROR_THRESHOLD.times do - assert_raises RedisClient::ConnectionError do - connect_to_redis!(host: 'thisdoesnotresolve') + assert_raises(RedisClient::ConnectionError) do + connect_to_redis!(host: "thisdoesnotresolve") end end - assert_raises RedisClient::CircuitOpenError do - connect_to_redis!(host: 'thisdoesnotresolve') + assert_raises(RedisClient::CircuitOpenError) do + connect_to_redis!(host: "thisdoesnotresolve") end Timecop.travel(ERROR_TIMEOUT + 1) do @@ -168,15 +172,15 @@ def test_query_instrumentation notified = false subscriber = Semian.subscribe do |event, resource, scope, adapter| notified = true - assert_equal :success, event - assert_equal Semian[:redis_testing], resource - assert_equal :query, scope - assert_equal :redis_client, adapter + assert_equal(:success, event) + assert_equal(Semian[:redis_testing], resource) + assert_equal(:query, scope) + assert_equal(:redis_client, adapter) end - client.call('get', 'foo') + client.call("get", "foo") - assert notified, 'No notifications has been emitted' + assert(notified, "No notifications has been emitted") ensure Semian.unsubscribe(subscriber) end @@ -188,37 +192,37 @@ def test_timeout_changes_when_half_open_and_configured_with_reads Timecop.freeze(0) do @proxy.downstream(:latency, latency: redis_timeout_ms + 200).apply do ERROR_THRESHOLD.times do - assert_raises RedisClient::TimeoutError do - client.call('get', 'foo') + assert_raises(RedisClient::TimeoutError) do + client.call("get", "foo") end end end - assert_raises RedisClient::CircuitOpenError do - client.call('get', 'foo') + assert_raises(RedisClient::CircuitOpenError) do + client.call("get", "foo") end end time_circuit_half_open = ERROR_TIMEOUT + 1 Timecop.travel(time_circuit_half_open) do assert_redis_timeout_in_delta(expected_timeout: half_open_resource_timeout) do - client.call('get', 'foo') + client.call("get", "foo") end end time_circuit_closed = time_circuit_half_open + ERROR_TIMEOUT + 1 Timecop.travel(time_circuit_closed) do - SUCCESS_THRESHOLD.times { client.call('get', 'foo') } + SUCCESS_THRESHOLD.times { client.call("get", "foo") } # Timeout has reset now that the Circuit is closed assert_redis_timeout_in_delta(expected_timeout: REDIS_TIMEOUT) do - client.call('get', 'foo') + client.call("get", "foo") end end - assert_equal REDIS_TIMEOUT, client.connect_timeout - assert_equal REDIS_TIMEOUT, client.read_timeout - assert_equal REDIS_TIMEOUT, client.write_timeout + assert_equal(REDIS_TIMEOUT, client.connect_timeout) + assert_equal(REDIS_TIMEOUT, client.read_timeout) + assert_equal(REDIS_TIMEOUT, client.write_timeout) end def test_timeout_changes_when_half_open_and_configured_with_writes_and_disconnects @@ -228,14 +232,14 @@ def test_timeout_changes_when_half_open_and_configured_with_writes_and_disconnec Timecop.freeze(0) do @proxy.downstream(:latency, latency: redis_timeout_ms + 200).apply do ERROR_THRESHOLD.times do - assert_raises RedisClient::TimeoutError do - client.call('set', 'foo', 1) + assert_raises(RedisClient::TimeoutError) do + client.call("set", "foo", 1) end end end - assert_raises RedisClient::CircuitOpenError do - client.call('set', 'foo', 1) + assert_raises(RedisClient::CircuitOpenError) do + client.call("set", "foo", 1) end end @@ -244,7 +248,7 @@ def test_timeout_changes_when_half_open_and_configured_with_writes_and_disconnec time_circuit_half_open = ERROR_TIMEOUT + 1 Timecop.travel(time_circuit_half_open) do assert_redis_timeout_in_delta(expected_timeout: half_open_resource_timeout) do - client.call('set', 'foo', 1) + client.call("set", "foo", 1) end end @@ -252,16 +256,16 @@ def test_timeout_changes_when_half_open_and_configured_with_writes_and_disconnec time_circuit_closed = time_circuit_half_open + ERROR_TIMEOUT + 1 Timecop.travel(time_circuit_closed) do - SUCCESS_THRESHOLD.times { client.call('set', 'foo', 1) } + SUCCESS_THRESHOLD.times { client.call("set", "foo", 1) } assert_redis_timeout_in_delta(expected_timeout: REDIS_TIMEOUT) do - client.call('set', 'foo', 1) + client.call("set", "foo", 1) end end - assert_equal REDIS_TIMEOUT, client.connect_timeout - assert_equal REDIS_TIMEOUT, client.read_timeout - assert_equal REDIS_TIMEOUT, client.write_timeout + assert_equal(REDIS_TIMEOUT, client.connect_timeout) + assert_equal(REDIS_TIMEOUT, client.read_timeout) + assert_equal(REDIS_TIMEOUT, client.write_timeout) end def test_timeout_doesnt_change_when_half_open_but_not_configured @@ -270,21 +274,21 @@ def test_timeout_doesnt_change_when_half_open_but_not_configured Timecop.freeze(0) do @proxy.downstream(:latency, latency: redis_timeout_ms + 200).apply do ERROR_THRESHOLD.times do - assert_raises RedisClient::TimeoutError do - client.call('get', 'foo') + assert_raises(RedisClient::TimeoutError) do + client.call("get", "foo") end end end - assert_raises RedisClient::CircuitOpenError do - client.call('get', 'foo') + assert_raises(RedisClient::CircuitOpenError) do + client.call("get", "foo") end end time_circuit_half_open = ERROR_TIMEOUT + 1 Timecop.travel(time_circuit_half_open) do assert_redis_timeout_in_delta(expected_timeout: REDIS_TIMEOUT) do - client.call('get', 'foo') + client.call("get", "foo") end end end @@ -293,8 +297,8 @@ def test_resource_acquisition_for_query client = connect_to_redis! Semian[:redis_testing].acquire do - assert_raises RedisClient::ResourceBusyError do - client.call('get', 'foo') + assert_raises(RedisClient::ResourceBusyError) do + client.call("get", "foo") end end end @@ -304,10 +308,10 @@ def test_resource_timeout_on_query client2 = connect_to_redis! @proxy.downstream(:latency, latency: redis_timeout_ms).apply do - background { client2.call('get', 'foo') } + background { client2.call("get", "foo") } - assert_raises RedisClient::ResourceBusyError do - client.call('get', 'foo') + assert_raises(RedisClient::ResourceBusyError) do + client.call("get", "foo") end end end @@ -316,26 +320,26 @@ def test_circuit_breaker_on_query client = connect_to_redis! client2 = connect_to_redis! - client.call('set', 'foo', 2) + client.call("set", "foo", 2) @proxy.downstream(:latency, latency: 1000).apply do - background { client2.call('get', 'foo') } + background { client2.call("get", "foo") } ERROR_THRESHOLD.times do - assert_raises RedisClient::ResourceBusyError do - client.call('get', 'foo') + assert_raises(RedisClient::ResourceBusyError) do + client.call("get", "foo") end end end yield_to_background - assert_raises RedisClient::CircuitOpenError do - client.call('get', 'foo') + assert_raises(RedisClient::CircuitOpenError) do + client.call("get", "foo") end Timecop.travel(ERROR_TIMEOUT + 1) do - assert_equal '2', client.call('get', 'foo') + assert_equal("2", client.call("get", "foo")) end end @@ -346,15 +350,15 @@ def new_client(**options) end def new_config(**options) - options[:host] = SemianConfig['toxiproxy_upstream_host'] if options[:host].nil? + options[:host] = SemianConfig["toxiproxy_upstream_host"] if options[:host].nil? semian_options = SEMIAN_OPTIONS.merge(options.delete(:semian) || {}) RedisClient.config(**{ - port: SemianConfig['redis_toxiproxy_port'], + port: SemianConfig["redis_toxiproxy_port"], reconnect_attempts: 0, db: 1, timeout: REDIS_TIMEOUT, semian: semian_options, - driver: redis_driver + driver: redis_driver, }.merge(options)) end @@ -369,17 +373,15 @@ def redis_timeout_ms @redis_timeout_ms ||= (REDIS_TIMEOUT * 1000).to_i end - def assert_redis_timeout_in_delta(expected_timeout:, delta: 0.1) + def assert_redis_timeout_in_delta(expected_timeout:, delta: 0.1, &block) latency = ((expected_timeout + 2 * delta) * 1000).to_i bench = Benchmark.measure do - assert_raises RedisClient::TimeoutError do - @proxy.downstream(:latency, latency: latency).apply do - yield - end + assert_raises(RedisClient::TimeoutError) do + @proxy.downstream(:latency, latency: latency).apply(&block) end end - assert_in_delta bench.real, expected_timeout, delta + assert_in_delta(bench.real, expected_timeout, delta) end end diff --git a/test/redis_test.rb b/test/redis_test.rb index dcb419fb5..7a3910595 100644 --- a/test/redis_test.rb +++ b/test/redis_test.rb @@ -1,5 +1,7 @@ -require 'test_helper' -require 'benchmark' +# frozen_string_literal: true + +require "test_helper" +require "benchmark" module RedisTests REDIS_TIMEOUT = 0.5 @@ -16,32 +18,35 @@ module RedisTests } attr_writer :threads + def setup @proxy = Toxiproxy[:semian_test_redis] Semian.destroy(:redis_testing) end def test_semian_identifier - assert_equal :redis_foo, new_redis(semian: {name: 'foo'})._client.semian_identifier - assert_equal :"redis_#{SemianConfig['toxiproxy_upstream_host']}:16379/1", new_redis(semian: {name: nil})._client.semian_identifier - assert_equal :'redis_example.com:42/1', new_redis(host: 'example.com', port: 42, semian: {name: nil})._client.semian_identifier + assert_equal(:redis_foo, new_redis(semian: { name: "foo" })._client.semian_identifier) + assert_equal(:"redis_#{SemianConfig["toxiproxy_upstream_host"]}:16379/1", + new_redis(semian: { name: nil })._client.semian_identifier) + assert_equal(:"redis_example.com:42/1", + new_redis(host: "example.com", port: 42, semian: { name: nil })._client.semian_identifier) end def test_client_alias redis = connect_to_redis! - assert_equal redis._client.semian_resource, redis.semian_resource - assert_equal redis._client.semian_identifier, redis.semian_identifier + assert_equal(redis._client.semian_resource, redis.semian_resource) + assert_equal(redis._client.semian_identifier, redis.semian_identifier) end def test_semian_can_be_disabled resource = Redis.new(semian: false)._client.semian_resource - assert_instance_of Semian::UnprotectedResource, resource + assert_instance_of(Semian::UnprotectedResource, resource) end def test_semian_resource_in_pipeline redis = connect_to_redis! redis.pipelined do |_pipeline| - assert_instance_of Semian::ProtectedResource, redis.semian_resource + assert_instance_of(Semian::ProtectedResource, redis.semian_resource) end end @@ -50,13 +55,13 @@ def test_connection_errors_open_the_circuit @proxy.downstream(:latency, latency: 600).apply do ERROR_THRESHOLD.times do - assert_raises ::Redis::TimeoutError do - client.get('foo') + assert_raises(::Redis::TimeoutError) do + client.get("foo") end end - assert_raises ::Redis::CircuitOpenError do - client.get('foo') + assert_raises(::Redis::CircuitOpenError) do + client.get("foo") end end end @@ -66,23 +71,23 @@ def test_connection_reset_does_not_open_the_circuit @proxy.downstream(:reset_peer).apply do ERROR_THRESHOLD.times do - assert_raises ::Redis::ConnectionError do - client.get('foo') + assert_raises(::Redis::ConnectionError) do + client.get("foo") end end - assert_raises ::Redis::ConnectionError do - client.get('foo') + assert_raises(::Redis::ConnectionError) do + client.get("foo") end end end def test_command_errors_does_not_open_the_circuit client = connect_to_redis! - client.hset('my_hash', 'foo', 'bar') + client.hset("my_hash", "foo", "bar") (ERROR_THRESHOLD * 2).times do - assert_raises Redis::CommandError do - client.get('my_hash') + assert_raises(Redis::CommandError) do + client.get("my_hash") end end end @@ -92,15 +97,15 @@ def test_command_errors_because_of_oom_do_open_the_circuit with_maxmemory(1) do ERROR_THRESHOLD.times do - exception = assert_raises ::Redis::OutOfMemoryError do - client.set('foo', 'bar') + exception = assert_raises(::Redis::OutOfMemoryError) do + client.set("foo", "bar") end - assert_equal :redis_testing, exception.semian_identifier + assert_equal(:redis_testing, exception.semian_identifier) end - assert_raises ::Redis::CircuitOpenError do - client.set('foo', 'bla') + assert_raises(::Redis::CircuitOpenError) do + client.set("foo", "bla") end end end @@ -110,14 +115,14 @@ def test_script_errors_because_of_oom_do_open_the_circuit with_maxmemory(1) do ERROR_THRESHOLD.times do - exception = assert_raises ::Redis::OutOfMemoryError do + exception = assert_raises(::Redis::OutOfMemoryError) do client.eval("return redis.call('set', 'foo', 'bar');") end - assert_equal :redis_testing, exception.semian_identifier + assert_equal(:redis_testing, exception.semian_identifier) end - assert_raises ::Redis::CircuitOpenError do + assert_raises(::Redis::CircuitOpenError) do client.eval("return redis.call('set', 'foo', 'bar');") end end @@ -129,14 +134,14 @@ def test_connect_instrumentation next unless event == :success notified = true - assert_equal Semian[:redis_testing], resource - assert_equal :connection, scope - assert_equal :redis, adapter + assert_equal(Semian[:redis_testing], resource) + assert_equal(:connection, scope) + assert_equal(:redis, adapter) end connect_to_redis! - assert notified, 'No notifications has been emitted' + assert(notified, "No notifications has been emitted") ensure Semian.unsubscribe(subscriber) end @@ -145,37 +150,37 @@ def test_resource_acquisition_for_connect connect_to_redis! Semian[:redis_testing].acquire do - error = assert_raises Redis::ResourceBusyError do + error = assert_raises(Redis::ResourceBusyError) do connect_to_redis! end - assert_equal :redis_testing, error.semian_identifier + assert_equal(:redis_testing, error.semian_identifier) end end def test_redis_connection_errors_are_tagged_with_the_resource_identifier @proxy.downstream(:latency, latency: 600).apply do - error = assert_raises ::Redis::TimeoutError do + error = assert_raises(::Redis::TimeoutError) do redis = connect_to_redis! - redis.get('foo') + redis.get("foo") end - assert_equal :redis_testing, error.semian_identifier + assert_equal(:redis_testing, error.semian_identifier) end end def test_other_redis_errors_are_not_tagged_with_the_resource_identifier client = connect_to_redis! - client.set('foo', 'bar') - error = assert_raises ::Redis::CommandError do - client.hget('foo', 'bar') + client.set("foo", "bar") + error = assert_raises(::Redis::CommandError) do + client.hget("foo", "bar") end - refute error.respond_to?(:semian_identifier) + refute_respond_to(error, :semian_identifier) end def test_resource_timeout_on_connect @proxy.downstream(:latency, latency: redis_timeout_ms).apply do background { connect_to_redis! } - assert_raises Redis::ResourceBusyError do + assert_raises(Redis::ResourceBusyError) do connect_to_redis! end end @@ -183,13 +188,13 @@ def test_resource_timeout_on_connect def test_dns_resolution_failures_open_circuit ERROR_THRESHOLD.times do - assert_raises Redis::ResolveError do - connect_to_redis!(host: 'thisdoesnotresolve') + assert_raises(Redis::ResolveError) do + connect_to_redis!(host: "thisdoesnotresolve") end end - assert_raises Redis::CircuitOpenError do - connect_to_redis!(host: 'thisdoesnotresolve') + assert_raises(Redis::CircuitOpenError) do + connect_to_redis!(host: "thisdoesnotresolve") end Timecop.travel(ERROR_TIMEOUT + 1) do @@ -203,12 +208,12 @@ def test_dns_resolution_failures_open_circuit "name or service not known", "Could not resolve hostname example.com: nodename nor servname provided, or not known", ].each do |message| - test_suffix = message.gsub(/\W/, '_').downcase + test_suffix = message.gsub(/\W/, "_").downcase define_method(:"test_dns_resolution_failure_#{test_suffix}") do Redis::Client.any_instance.expects(:raw_connect).raises(message) assert_raises Redis::ResolveError do - connect_to_redis!(host: 'example.com') + connect_to_redis!(host: "example.com") end end end @@ -218,7 +223,7 @@ def test_circuit_breaker_on_connect background { connect_to_redis! } ERROR_THRESHOLD.times do - assert_raises Redis::ResourceBusyError do + assert_raises(Redis::ResourceBusyError) do connect_to_redis! end end @@ -226,7 +231,7 @@ def test_circuit_breaker_on_connect yield_to_background - assert_raises Redis::CircuitOpenError do + assert_raises(Redis::CircuitOpenError) do connect_to_redis! end @@ -241,15 +246,15 @@ def test_query_instrumentation notified = false subscriber = Semian.subscribe do |event, resource, scope, adapter| notified = true - assert_equal :success, event - assert_equal Semian[:redis_testing], resource - assert_equal :query, scope - assert_equal :redis, adapter + assert_equal(:success, event) + assert_equal(Semian[:redis_testing], resource) + assert_equal(:query, scope) + assert_equal(:redis, adapter) end - client.get('foo') + client.get("foo") - assert notified, 'No notifications has been emitted' + assert(notified, "No notifications has been emitted") ensure Semian.unsubscribe(subscriber) end @@ -261,39 +266,39 @@ def test_timeout_changes_when_half_open_and_configured_with_reads Timecop.freeze(0) do @proxy.downstream(:latency, latency: redis_timeout_ms + 200).apply do ERROR_THRESHOLD.times do - assert_raises ::Redis::TimeoutError do - client.get('foo') + assert_raises(::Redis::TimeoutError) do + client.get("foo") end end end - assert_raises ::Redis::CircuitOpenError do - client.get('foo') + assert_raises(::Redis::CircuitOpenError) do + client.get("foo") end end time_circuit_half_open = ERROR_TIMEOUT + 1 Timecop.travel(time_circuit_half_open) do assert_redis_timeout_in_delta(expected_timeout: half_open_resource_timeout) do - client.get('foo') + client.get("foo") end end time_circuit_closed = time_circuit_half_open + ERROR_TIMEOUT + 1 Timecop.travel(time_circuit_closed) do - SUCCESS_THRESHOLD.times { client.get('foo') } + SUCCESS_THRESHOLD.times { client.get("foo") } # Timeout has reset now that the Circuit is closed assert_redis_timeout_in_delta(expected_timeout: REDIS_TIMEOUT) do - client.get('foo') + client.get("foo") end end - assert_equal REDIS_TIMEOUT, client.instance_variable_get(:@client).options[:timeout] - assert_equal REDIS_TIMEOUT, client.instance_variable_get(:@client).options[:connect_timeout] - assert_equal REDIS_TIMEOUT, client.instance_variable_get(:@client).options[:read_timeout] - assert_equal REDIS_TIMEOUT, client.instance_variable_get(:@client).options[:write_timeout] - assert_equal REDIS_TIMEOUT, client.instance_variable_get(:@client).timeout + assert_equal(REDIS_TIMEOUT, client.instance_variable_get(:@client).options[:timeout]) + assert_equal(REDIS_TIMEOUT, client.instance_variable_get(:@client).options[:connect_timeout]) + assert_equal(REDIS_TIMEOUT, client.instance_variable_get(:@client).options[:read_timeout]) + assert_equal(REDIS_TIMEOUT, client.instance_variable_get(:@client).options[:write_timeout]) + assert_equal(REDIS_TIMEOUT, client.instance_variable_get(:@client).timeout) end def test_timeout_changes_when_half_open_and_configured_with_writes_and_disconnects @@ -303,14 +308,14 @@ def test_timeout_changes_when_half_open_and_configured_with_writes_and_disconnec Timecop.freeze(0) do @proxy.downstream(:latency, latency: redis_timeout_ms + 200).apply do ERROR_THRESHOLD.times do - assert_raises ::Redis::TimeoutError do - client.set('foo', 1) + assert_raises(::Redis::TimeoutError) do + client.set("foo", 1) end end end - assert_raises ::Redis::CircuitOpenError do - client.set('foo', 1) + assert_raises(::Redis::CircuitOpenError) do + client.set("foo", 1) end end @@ -319,7 +324,7 @@ def test_timeout_changes_when_half_open_and_configured_with_writes_and_disconnec time_circuit_half_open = ERROR_TIMEOUT + 1 Timecop.travel(time_circuit_half_open) do assert_redis_timeout_in_delta(expected_timeout: half_open_resource_timeout) do - client.set('foo', 1) + client.set("foo", 1) end end @@ -327,18 +332,18 @@ def test_timeout_changes_when_half_open_and_configured_with_writes_and_disconnec time_circuit_closed = time_circuit_half_open + ERROR_TIMEOUT + 1 Timecop.travel(time_circuit_closed) do - SUCCESS_THRESHOLD.times { client.set('foo', 1) } + SUCCESS_THRESHOLD.times { client.set("foo", 1) } assert_redis_timeout_in_delta(expected_timeout: REDIS_TIMEOUT) do - client.set('foo', 1) + client.set("foo", 1) end end - assert_equal REDIS_TIMEOUT, client.instance_variable_get(:@client).options[:timeout] - assert_equal REDIS_TIMEOUT, client.instance_variable_get(:@client).options[:connect_timeout] - assert_equal REDIS_TIMEOUT, client.instance_variable_get(:@client).options[:read_timeout] - assert_equal REDIS_TIMEOUT, client.instance_variable_get(:@client).options[:write_timeout] - assert_equal REDIS_TIMEOUT, client.instance_variable_get(:@client).timeout + assert_equal(REDIS_TIMEOUT, client.instance_variable_get(:@client).options[:timeout]) + assert_equal(REDIS_TIMEOUT, client.instance_variable_get(:@client).options[:connect_timeout]) + assert_equal(REDIS_TIMEOUT, client.instance_variable_get(:@client).options[:read_timeout]) + assert_equal(REDIS_TIMEOUT, client.instance_variable_get(:@client).options[:write_timeout]) + assert_equal(REDIS_TIMEOUT, client.instance_variable_get(:@client).timeout) end def test_timeout_doesnt_change_when_half_open_but_not_configured @@ -347,21 +352,21 @@ def test_timeout_doesnt_change_when_half_open_but_not_configured Timecop.freeze(0) do @proxy.downstream(:latency, latency: redis_timeout_ms + 200).apply do ERROR_THRESHOLD.times do - assert_raises ::Redis::TimeoutError do - client.get('foo') + assert_raises(::Redis::TimeoutError) do + client.get("foo") end end end - assert_raises ::Redis::CircuitOpenError do - client.get('foo') + assert_raises(::Redis::CircuitOpenError) do + client.get("foo") end end time_circuit_half_open = ERROR_TIMEOUT + 1 Timecop.travel(time_circuit_half_open) do assert_redis_timeout_in_delta(expected_timeout: REDIS_TIMEOUT) do - client.get('foo') + client.get("foo") end end end @@ -370,8 +375,8 @@ def test_resource_acquisition_for_query client = connect_to_redis! Semian[:redis_testing].acquire do - assert_raises Redis::ResourceBusyError do - client.get('foo') + assert_raises(Redis::ResourceBusyError) do + client.get("foo") end end end @@ -381,10 +386,10 @@ def test_resource_timeout_on_query client2 = connect_to_redis! @proxy.downstream(:latency, latency: redis_timeout_ms).apply do - background { client2.get('foo') } + background { client2.get("foo") } - assert_raises Redis::ResourceBusyError do - client.get('foo') + assert_raises(Redis::ResourceBusyError) do + client.get("foo") end end end @@ -393,41 +398,41 @@ def test_circuit_breaker_on_query client = connect_to_redis! client2 = connect_to_redis! - client.set('foo', 2) + client.set("foo", 2) @proxy.downstream(:latency, latency: 1000).apply do - background { client2.get('foo') } + background { client2.get("foo") } ERROR_THRESHOLD.times do - assert_raises Redis::ResourceBusyError do - client.get('foo') + assert_raises(Redis::ResourceBusyError) do + client.get("foo") end end end yield_to_background - assert_raises Redis::CircuitOpenError do - client.get('foo') + assert_raises(Redis::CircuitOpenError) do + client.get("foo") end Timecop.travel(ERROR_TIMEOUT + 1) do - assert_equal '2', client.get('foo') + assert_equal("2", client.get("foo")) end end private def new_redis(options = {}) - options[:host] = SemianConfig['toxiproxy_upstream_host'] if options[:host].nil? + options[:host] = SemianConfig["toxiproxy_upstream_host"] if options[:host].nil? semian_options = SEMIAN_OPTIONS.merge(options.delete(:semian) || {}) Redis.new({ - port: SemianConfig['redis_toxiproxy_port'], + port: SemianConfig["redis_toxiproxy_port"], reconnect_attempts: 0, db: 1, timeout: REDIS_TIMEOUT, semian: semian_options, - driver: redis_driver + driver: redis_driver, }.merge(options)) end @@ -439,14 +444,14 @@ def connect_to_redis!(semian_options = {}) end def with_maxmemory(bytes) - client = connect_to_redis!(name: 'maxmemory') + client = connect_to_redis!(name: "maxmemory") - _, old = client.config('get', 'maxmemory') + _, old = client.config("get", "maxmemory") begin - client.config('set', 'maxmemory', bytes) + client.config("set", "maxmemory", bytes) yield ensure - client.config('set', 'maxmemory', old) + client.config("set", "maxmemory", old) end end @@ -454,17 +459,15 @@ def redis_timeout_ms @redis_timeout_ms ||= (REDIS_TIMEOUT * 1000).to_i end - def assert_redis_timeout_in_delta(expected_timeout:, delta: 0.1) + def assert_redis_timeout_in_delta(expected_timeout:, delta: 0.1, &block) latency = ((expected_timeout + 2 * delta) * 1000).to_i bench = Benchmark.measure do - assert_raises Redis::TimeoutError do - @proxy.downstream(:latency, latency: latency).apply do - yield - end + assert_raises(Redis::TimeoutError) do + @proxy.downstream(:latency, latency: latency).apply(&block) end end - assert_in_delta bench.real, expected_timeout, delta + assert_in_delta(bench.real, expected_timeout, delta) end end diff --git a/test/resource_test.rb b/test/resource_test.rb index b38cc7547..44d198dfc 100644 --- a/test/resource_test.rb +++ b/test/resource_test.rb @@ -1,4 +1,6 @@ -require 'test_helper' +# frozen_string_literal: true + +require "test_helper" class TestResource < Minitest::Test include ResourceHelper @@ -14,59 +16,60 @@ def setup def teardown destroy_resources - signal_workers('KILL') + signal_workers("KILL") Process.waitall end def test_initialize_invalid_args - assert_raises TypeError do - create_resource 123, tickets: 2 + assert_raises(TypeError) do + create_resource(123, tickets: 2) end - assert_raises ArgumentError do - create_resource :testing, tickets: -1 + assert_raises(ArgumentError) do + create_resource(:testing, tickets: -1) end - assert_raises ArgumentError do - create_resource :testing, tickets: 1_000_000 + assert_raises(ArgumentError) do + create_resource(:testing, tickets: 1_000_000) end - assert_raises TypeError do - create_resource :testing, tickets: 2, permissions: 'test' + assert_raises(TypeError) do + create_resource(:testing, tickets: 2, permissions: "test") end end def test_initialize_with_float expected_warning = /semian ticket value 1\.000000 is a float, converting to fixnum/ with_fake_std_error(warn_message: expected_warning) do - resource = create_resource :testing, tickets: 1.0 - assert resource - assert_equal 1, resource.tickets + resource = create_resource(:testing, tickets: 1.0) + assert(resource) + assert_equal(1, resource.tickets) end end def test_max_tickets - assert Semian::MAX_TICKETS > 0 + assert(Semian::MAX_TICKETS > 0) end def test_register - create_resource :testing, tickets: 2 + create_resource(:testing, tickets: 2) end def test_register_with_quota - create_resource :testing, quota: 0.5 + create_resource(:testing, quota: 0.5) end def test_unregister_past_0 workers = 10 - resource = Semian.register(:testing, tickets: workers * 2, error_threshold: 0, error_timeout: 0, success_threshold: 0) + resource = Semian.register(:testing, tickets: workers * 2, error_threshold: 0, error_timeout: 0, + success_threshold: 0) fork_workers(count: workers, tickets: 0, timeout: 0.5, wait_for_timeout: true) do Semian.unregister(:testing) end Semian.unregister(:testing) - signal_workers('TERM') + signal_workers("TERM") Process.waitall - assert_equal 0, resource.registered_workers + assert_equal(0, resource.registered_workers) end def test_reset_registered_workers @@ -75,13 +78,13 @@ def test_reset_registered_workers fork_workers(count: workers - 1, tickets: 0, timeout: 0.5, wait_for_timeout: true) - assert_equal workers, resource.registered_workers + assert_equal(workers, resource.registered_workers) resource.bulkhead.reset_registered_workers! - assert_equal 0, resource.registered_workers + assert_equal(0, resource.registered_workers) - signal_workers('TERM') + signal_workers("TERM") Process.waitall - assert_equal 0, resource.registered_workers + assert_equal(0, resource.registered_workers) end def test_exactly_one_register_with_quota @@ -91,78 +94,79 @@ def test_exactly_one_register_with_quota Semian::Resource.instance(:testing, quota: 0.5) end - assert_equal 1, r.tickets + assert_equal(1, r.tickets) r.destroy end def test_register_with_invalid_quota - assert_raises ArgumentError do - create_resource :testing, quota: 2.0 + assert_raises(ArgumentError) do + create_resource(:testing, quota: 2.0) end - assert_raises ArgumentError do - create_resource :testing, quota: 0 + assert_raises(ArgumentError) do + create_resource(:testing, quota: 0) end - assert_raises ArgumentError do - create_resource :testing, quota: -1.0 + assert_raises(ArgumentError) do + create_resource(:testing, quota: -1.0) end end def test_register_with_quota_and_tickets_raises - assert_raises ArgumentError do - create_resource :testing, tickets: 2, quota: 0.5 + assert_raises(ArgumentError) do + create_resource(:testing, tickets: 2, quota: 0.5) end end def test_register_with_neither_quota_nor_tickets_raises - assert_raises ArgumentError do + assert_raises(ArgumentError) do Semian::Resource.new(:testing) end end def test_register_with_no_tickets_raises - assert_raises Semian::SyscallError do - create_resource :test_raises, tickets: 0 + assert_raises(Semian::SyscallError) do + create_resource(:test_raises, tickets: 0) end end def test_acquire acquired = false - resource = create_resource :testing, tickets: 1 + resource = create_resource(:testing, tickets: 1) resource.acquire { acquired = true } - assert acquired + assert(acquired) end def test_acquire_return_val - resource = create_resource :testing, tickets: 1 + resource = create_resource(:testing, tickets: 1) val = resource.acquire { 1234 } - assert_equal 1234, val + assert_equal(1234, val) end def test_acquire_timeout fork_workers(count: 2, tickets: 1, timeout: 1, wait_for_timeout: true) - signal_workers('TERM') + signal_workers("TERM") timeouts = count_worker_timeouts - assert 1, timeouts + assert_equal(1, timeouts) end def test_acquire_timeout_override skip("Never tested correctly") + fork_workers(count: 1, tickets: 1, timeout: 0.5, wait_for_timeout: true) do - sleep 0.6 + sleep(0.6) end fork_workers(count: 1, tickets: 1, timeout: 1, wait_for_timeout: true) - signal_workers('TERM') + signal_workers("TERM") timeouts = count_worker_timeouts - assert_equal 0, timeouts + assert_equal(0, timeouts) end def test_acquire_with_fork - resource = create_resource :testing, tickets: 2, timeout: 0.5 + resource = create_resource(:testing, tickets: 2, timeout: 0.5) resource.acquire do fork do @@ -170,9 +174,9 @@ def test_acquire_with_fork begin resource.acquire {} rescue Semian::TimeoutError - exit! 100 + exit!(100) end - exit! 0 + exit!(0) end end timeouts = count_worker_timeouts @@ -185,26 +189,26 @@ def test_quota_acquire quota = 0.5 workers = 9 - resource = create_resource :testing, quota: quota, timeout: 0.1 + resource = create_resource(:testing, quota: quota, timeout: 0.1) fork_workers(count: workers, quota: quota, wait_for_timeout: true) # Ensure that the number of tickets is correct and none are remaining - assert_equal quota * (workers + 1), resource.tickets - assert_equal 0, resource.count + assert_equal(quota * (workers + 1), resource.tickets) + assert_equal(0, resource.count) # Ensure that no more tickets may be allocated - assert_raises Semian::TimeoutError do + assert_raises(Semian::TimeoutError) do resource.acquire {} end - signal_workers('TERM') + signal_workers("TERM") # Ensure the correct number of processes timed out timeouts = count_worker_timeouts - assert_equal workers - ((1 - quota) * workers).ceil, timeouts + assert_equal(workers - ((1 - quota) * workers).ceil, timeouts) # Ensure that the tickets were released - assert_equal resource.tickets, resource.count + assert_equal(resource.tickets, resource.count) end def test_quota_increase @@ -214,7 +218,7 @@ def test_quota_increase # Spawn some workers with an initial quota fork_workers(count: workers - 1, quota: quota, timeout: 0.5, wait_for_timeout: true) - resource = create_resource :testing, quota: new_quota, timeout: 0.1 + resource = create_resource(:testing, quota: new_quota, timeout: 0.1) assert_equal((workers * new_quota).ceil, resource.tickets) end @@ -226,12 +230,12 @@ def test_quota_decrease # Spawn some workers with an initial quota fork_workers(count: workers - 1, quota: quota, timeout: 0.5, wait_for_timeout: true) do - sleep 1 + sleep(1) end # We need to signal here to be able to enter the critical section - signal_workers('TERM') - resource = create_resource :testing, quota: new_quota, timeout: 0.1 + signal_workers("TERM") + resource = create_resource(:testing, quota: new_quota, timeout: 0.1) assert_equal((workers * new_quota).ceil, resource.tickets) end @@ -240,7 +244,7 @@ def test_quota_sets_tickets_from_workers quota = 0.5 workers = 50 - resource = create_resource :testing, quota: quota, timeout: 0.1 + resource = create_resource(:testing, quota: quota, timeout: 0.1) fork_workers(count: workers - 1, quota: quota, wait_for_timeout: true) assert_equal((workers * quota).ceil, resource.tickets) @@ -250,7 +254,7 @@ def test_quota_adjust_tickets_on_new_workers quota = 0.5 workers = 50 - resource = create_resource :testing, quota: quota, timeout: 0.1 + resource = create_resource(:testing, quota: quota, timeout: 0.1) # Spawn some workers to get a basis for the quota fork_workers(count: workers - 1, quota: quota, wait_for_timeout: true) @@ -265,27 +269,27 @@ def test_quota_adjust_tickets_on_kill quota = 0.5 workers = 50 - resource = create_resource :testing, quota: quota, timeout: 0.1 + resource = create_resource(:testing, quota: quota, timeout: 0.1) # Spawn some workers to get a basis for the quota fork_workers(count: workers - 1, quota: quota, wait_for_timeout: true) assert_equal((workers * quota).ceil, resource.tickets) # Signal and wait for the workers to quit - signal_workers('KILL') + signal_workers("KILL") Process.waitall # Number of tickets should be unchanged until resource is created assert_equal((workers * quota).ceil, resource.tickets) - resource = create_resource :testing, quota: quota, timeout: 0.1 - assert_equal 1, resource.tickets + resource = create_resource(:testing, quota: quota, timeout: 0.1) + assert_equal(1, resource.tickets) end def test_quota_minimum_one_ticket - resource = create_resource :testing, quota: 0.1, timeout: 0.1 + resource = create_resource(:testing, quota: 0.1, timeout: 0.1) - assert_equal 1, resource.tickets + assert_equal(1, resource.tickets) end def test_switch_static_tickets_to_quota @@ -294,50 +298,50 @@ def test_switch_static_tickets_to_quota # Fork a large number of workers using static ticket strategy fork_workers(count: workers - 1, tickets: 5, timeout: 0.5, wait_for_timeout: true) do - sleep 1 + sleep(1) end # Signal static workers to shut down - signal_workers('TERM') + signal_workers("TERM") # Create a quota based worker, and ensure it accounts for the static # workers that haven't shut down yet - resource = create_resource :testing, quota: quota, timeout: 0.1 + resource = create_resource(:testing, quota: quota, timeout: 0.1) assert_equal((quota * workers).ceil, resource.tickets) # Let the static workers shut down - sleep 2 + sleep(2) # Create a new resource, and ensure the static workers are no longer # accounted for - resource = create_resource :testing, quota: quota, timeout: 0.1 + resource = create_resource(:testing, quota: quota, timeout: 0.1) assert_equal((quota * 2).ceil, resource.tickets) end def test_acquire_releases_on_kill - resource = create_resource :testing, tickets: 1, timeout: 0.1 + resource = create_resource(:testing, tickets: 1, timeout: 0.1) acquired = false # Ghetto process synchronization - file = Tempfile.new('semian') + file = Tempfile.new("semian") path = file.path file.close! pid = fork do resource.acquire do FileUtils.touch(path) - sleep 1000 + sleep(1000) end end - sleep 0.1 until File.exist?(path) - assert_raises Semian::TimeoutError do + sleep(0.1) until File.exist?(path) + assert_raises(Semian::TimeoutError) do resource.acquire {} end Process.kill("KILL", pid) resource.acquire { acquired = true } - assert acquired + assert(acquired) Process.wait ensure @@ -347,30 +351,30 @@ def test_acquire_releases_on_kill def test_get_worker_count workers = rand(5..20) fork_workers(count: workers - 1, tickets: 1, timeout: 0.1, wait_for_timeout: true) - resource = create_resource :testing, tickets: 1 + resource = create_resource(:testing, tickets: 1) assert_equal(workers, resource.registered_workers) end def test_get_resource_key - resource = create_resource :testing, tickets: 2 - assert_equal('0x874714f2', resource.key) + resource = create_resource(:testing, tickets: 2) + assert_equal("0x874714f2", resource.key) end def test_count - resource = create_resource :testing, tickets: 2 + resource = create_resource(:testing, tickets: 2) acquired = false resource.acquire do acquired = true - assert_equal 1, resource.count - assert_equal 2, resource.tickets + assert_equal(1, resource.count) + assert_equal(2, resource.tickets) end - assert acquired + assert(acquired) end def test_sem_undo - resource = create_resource :testing, tickets: 1 + resource = create_resource(:testing, tickets: 1) # Ensure we don't hit ERANGE errors caused by lack of SEM_UNDO on semop* calls # by doing an acquire > SEMVMX (32767) times: @@ -384,34 +388,34 @@ def test_sem_undo end def test_destroy - resource = create_resource :testing, tickets: 1 + resource = create_resource(:testing, tickets: 1) resource.destroy - assert_raises Semian::SyscallError do + assert_raises(Semian::SyscallError) do resource.acquire {} end end def test_destroy_already_destroyed - resource = create_resource :testing, tickets: 1 + resource = create_resource(:testing, tickets: 1) 100.times do resource.destroy end end def test_permissions - resource = create_resource :testing, permissions: 0o600, tickets: 1 + resource = create_resource(:testing, permissions: 0600, tickets: 1) semid = resource.semid - `ipcs -s`.lines.each do |line| + %x(ipcs -s).lines.each do |line| if /\s#{semid}\s/.match(line) - assert_equal '600', line.split[3] + assert_equal("600", line.split[3]) end end - resource = create_resource :testing, permissions: 0o660, tickets: 1 + resource = create_resource(:testing, permissions: 0660, tickets: 1) semid = resource.semid - `ipcs -s`.lines.each do |line| + %x(ipcs -s).lines.each do |line| if /\s#{semid}\s/.match(line) - assert_equal '660', line.split[3] + assert_equal("660", line.split[3]) end end end @@ -419,75 +423,75 @@ def test_permissions def test_namespace previous_namespace = Semian.namespace - resource = create_resource :testing, permissions: 0o600, tickets: 1 + resource = create_resource(:testing, permissions: 0600, tickets: 1) key = resource.key Semian.destroy(:testing) - resource = create_resource :testing, permissions: 0o600, tickets: 1 - assert_equal key, resource.key + resource = create_resource(:testing, permissions: 0600, tickets: 1) + assert_equal(key, resource.key) Semian.destroy(:testing) Semian.namespace = "testing" - resource = create_resource :testing, permissions: 0o600, tickets: 1 - refute_equal key, resource.key + resource = create_resource(:testing, permissions: 0600, tickets: 1) + refute_equal(key, resource.key) Semian.destroy(:testing) ensure Semian.namespace = previous_namespace end def test_resize_tickets_increase - resource = create_resource :testing, tickets: 1 + resource = create_resource(:testing, tickets: 1) - assert_equal resource.tickets, resource.count - assert_equal 1, resource.count + assert_equal(resource.tickets, resource.count) + assert_equal(1, resource.count) fork_workers(count: 1, tickets: 5, timeout: 1, wait_for_timeout: true) - signal_workers('TERM') + signal_workers("TERM") Process.waitall - assert_equal resource.tickets, resource.count - assert_equal 5, resource.count + assert_equal(resource.tickets, resource.count) + assert_equal(5, resource.count) end def test_resize_tickets_decrease - resource = create_resource :testing, tickets: 5 + resource = create_resource(:testing, tickets: 5) - assert_equal resource.tickets, resource.count - assert_equal 5, resource.count + assert_equal(resource.tickets, resource.count) + assert_equal(5, resource.count) fork_workers(count: 1, tickets: 1, timeout: 1, wait_for_timeout: true) - signal_workers('TERM') + signal_workers("TERM") Process.waitall - assert_equal resource.tickets, resource.count - assert_equal 1, resource.count + assert_equal(resource.tickets, resource.count) + assert_equal(1, resource.count) end def test_resize_tickets_decrease_with_fork tickets = 10 workers = 50 - resource = create_resource :testing, tickets: tickets + resource = create_resource(:testing, tickets: tickets) # Need to have the processes sleep for a bit after they are signalled to die fork_workers(count: workers, tickets: 0, wait_for_timeout: true) do - sleep 2 + sleep(2) end assert_equal(tickets, resource.tickets) assert_equal(0, resource.count) # Signal the workers to quit - signal_workers('TERM') + signal_workers("TERM") # Request the ticket count be adjusted immediately # This should only return once the ticket count has been adjusted # This is sort of racey, because it's waiting on enough workers to quit # So that it has room to adjust the ticket count to the desired value. # This must happen in less than 5 seconds, or an internal timeout occurs. - resource = create_resource :testing, tickets: (tickets / 2).floor + resource = create_resource(:testing, tickets: (tickets / 2).floor) # Immediately on the above call returning, the tickets should be correct assert_equal((tickets / 2).floor, resource.tickets) @@ -501,14 +505,14 @@ def test_multiple_register_with_fork tickets = 5 fork_workers(resource: :testing, count: count, tickets: tickets, wait_for_timeout: true) - assert_equal 5, create_resource(:testing, tickets: 0).tickets - assert_equal 0, create_resource(:testing, tickets: 0).count + assert_equal(5, create_resource(:testing, tickets: 0).tickets) + assert_equal(0, create_resource(:testing, tickets: 0).count) - signal_workers('TERM') + signal_workers("TERM") timeouts = count_worker_timeouts - assert_equal 5, create_resource(:testing, tickets: 0).count - assert_equal 0, timeouts + assert_equal(5, create_resource(:testing, tickets: 0).count) + assert_equal(0, timeouts) end def create_resource(name, **kwargs) @@ -520,12 +524,11 @@ def create_resource(name, **kwargs) def destroy_resources return unless @resources + @resources.each do |resource| - begin - resource.destroy - rescue - nil - end + resource.destroy + rescue + nil end @resources = [] end @@ -537,39 +540,37 @@ def destroy_resources # and workers must be cleaned up between tests by the teardown script # An exit value of 100 is to keep track of timeouts, 0 for success. def fork_workers(count:, resource: :testing, quota: nil, tickets: nil, timeout: 0.1, wait_for_timeout: false) - fail 'Must provide at least one of tickets or quota' unless tickets || quota + raise("Must provide at least one of tickets or quota") unless tickets || quota @workers ||= [] count.times do @workers << fork do - begin - resource = Semian::Resource.new(resource.to_sym, quota: quota, tickets: tickets, timeout: timeout) + resource = Semian::Resource.new(resource.to_sym, quota: quota, tickets: tickets, timeout: timeout) - Signal.trap('TERM') do - yield if block_given? - exit! 0 - end + Signal.trap("TERM") do + yield if block_given? + exit!(0) + end - # Hold the resource until signalled - resource.acquire do - sleep - end - rescue Semian::TimeoutError - Signal.trap('TERM') do - # Still sleep (in the yield) to avoid SIGCHLD, which makes semtimedop get interrupted - yield if block_given? - exit! 100 - end + # Hold the resource until signalled + resource.acquire do sleep - rescue StandardError => e - puts "[ERROR] Unhandled exception occurred in worker" - puts "Class: #{e.class}" - puts "Message: #{e}" - puts "---Backtrace---" - puts e.backtrace.join("\n") - puts "---------------" - exit! 2 end + rescue Semian::TimeoutError + Signal.trap("TERM") do + # Still sleep (in the yield) to avoid SIGCHLD, which makes semtimedop get interrupted + yield if block_given? + exit!(100) + end + sleep + rescue StandardError => e + puts "[ERROR] Unhandled exception occurred in worker" + puts "Class: #{e.class}" + puts "Message: #{e}" + puts "---Backtrace---" + puts e.backtrace.join("\n") + puts "---------------" + exit!(2) end end sleep((count / 2.0).ceil * timeout + EPSILON) if wait_for_timeout # give time for threads to timeout @@ -582,12 +583,11 @@ def count_worker_timeouts # Signals all workers def signal_workers(signal, delete: true) return unless @workers + @workers.each do |worker| - begin - Process.kill(signal, worker) - rescue - nil - end + Process.kill(signal, worker) + rescue + nil end @workers = [] if delete end @@ -613,9 +613,9 @@ def with_fake_std_error(warn_message: nil) $stderr = fake_std_err yield if warn_message - assert_match warn_message, fake_std_err.messages[0] + assert_match(warn_message, fake_std_err.messages[0]) end ensure - $std_err = original_stderr # rubocop:disable GlobalVars + $std_err = original_stderr # rubocop:disable Style/GlobalVars end end diff --git a/test/semian_test.rb b/test/semian_test.rb index 547467769..316254e03 100644 --- a/test/semian_test.rb +++ b/test/semian_test.rb @@ -1,4 +1,6 @@ -require 'test_helper' +# frozen_string_literal: true + +require "test_helper" class TestSemian < Minitest::Test def setup @@ -9,13 +11,13 @@ def setup def test_unsupported_acquire_yields acquired = false - Semian.register :testing, tickets: 1, error_threshold: 1, error_timeout: 2, success_threshold: 1 + Semian.register(:testing, tickets: 1, error_threshold: 1, error_timeout: 2, success_threshold: 1) Semian[:testing].acquire { acquired = true } - assert acquired + assert(acquired) end def test_register_with_circuit_breaker_missing_options - exception = assert_raises ArgumentError do + exception = assert_raises(ArgumentError) do Semian.register( :testing, error_threshold: 2, @@ -23,9 +25,7 @@ def test_register_with_circuit_breaker_missing_options bulkhead: false, ) end - assert_equal \ - exception.message, - "Missing required arguments for Semian: [:success_threshold]" + assert_equal("Missing required arguments for Semian: [:success_threshold]", exception.message) end def test_register_with_thread_safety_enabled @@ -39,8 +39,8 @@ def test_register_with_thread_safety_enabled thread_safety_disabled: false, ) - assert resource, Semian[:testing] - assert resource.circuit_breaker.state.instance_of?(Semian::ThreadSafe::State) + assert_equal(resource, Semian[:testing]) + assert_instance_of(Semian::ThreadSafe::State, resource.circuit_breaker.state) end def test_register_with_thread_safety_disabled @@ -54,22 +54,23 @@ def test_register_with_thread_safety_disabled thread_safety_disabled: true, ) - assert resource, Semian[:testing] - assert resource.circuit_breaker.state.instance_of?(Semian::Simple::State) + assert_equal(resource, Semian[:testing]) + assert_instance_of(Semian::Simple::State, resource.circuit_breaker.state) end def test_register_with_bulkhead_missing_options - exception = assert_raises ArgumentError do + exception = assert_raises(ArgumentError) do Semian.register( :testing, circuit_breaker: false, ) end - assert_equal "Semian configuration require either the :ticket or :quota parameter, you provided neither", exception.message + assert_equal("Semian configuration require either the :ticket or :quota parameter, you provided neither", + exception.message) end def test_register_with_exclusive_options - exception = assert_raises ArgumentError do + exception = assert_raises(ArgumentError) do Semian.register( :testing, tickets: 42, @@ -77,30 +78,31 @@ def test_register_with_exclusive_options circuit_breaker: false, ) end - assert_equal "Semian configuration require either the :ticket or :quota parameter, you provided both", exception.message + assert_equal("Semian configuration require either the :ticket or :quota parameter, you provided both", + exception.message) end def test_unsuported_constants - assert defined?(Semian::BaseError) - assert defined?(Semian::SyscallError) - assert defined?(Semian::TimeoutError) - assert defined?(Semian::InternalError) - assert defined?(Semian::Resource) + assert(defined?(Semian::BaseError)) + assert(defined?(Semian::SyscallError)) + assert(defined?(Semian::TimeoutError)) + assert(defined?(Semian::InternalError)) + assert(defined?(Semian::Resource)) end def test_disabled_via_env_var - ENV['SEMIAN_SEMAPHORES_DISABLED'] = '1' + ENV["SEMIAN_SEMAPHORES_DISABLED"] = "1" - refute Semian.semaphores_enabled? + refute_predicate(Semian, :semaphores_enabled?) ensure - ENV.delete('SEMIAN_SEMAPHORES_DISABLED') + ENV.delete("SEMIAN_SEMAPHORES_DISABLED") end def test_disabled_via_semian_wide_env_var - ENV['SEMIAN_DISABLED'] = '1' + ENV["SEMIAN_DISABLED"] = "1" - refute Semian.semaphores_enabled? + refute_predicate(Semian, :semaphores_enabled?) ensure - ENV.delete('SEMIAN_DISABLED') + ENV.delete("SEMIAN_DISABLED") end end diff --git a/test/simple_integer_test.rb b/test/simple_integer_test.rb index 2df824392..a151ddb8f 100644 --- a/test/simple_integer_test.rb +++ b/test/simple_integer_test.rb @@ -1,4 +1,6 @@ -require 'test_helper' +# frozen_string_literal: true + +require "test_helper" class TestSimpleInteger < Minitest::Test def setup @@ -14,7 +16,7 @@ def test_access_value assert_equal(0, @integer.value) @integer.value = 99 assert_equal(99, @integer.value) - time_now = (Time.now).to_i + time_now = Time.now.to_i @integer.value = time_now assert_equal(time_now, @integer.value) @integer.value = 6 diff --git a/test/simple_sliding_window_test.rb b/test/simple_sliding_window_test.rb index 2e10d18e2..bc77bca05 100644 --- a/test/simple_sliding_window_test.rb +++ b/test/simple_sliding_window_test.rb @@ -1,4 +1,6 @@ -require 'test_helper' +# frozen_string_literal: true + +require "test_helper" class TestSimpleSlidingWindow < Minitest::Test def setup @@ -25,8 +27,8 @@ def test_sliding_window_edge_falloff end def resize_to_less_than_1_raises - assert_raises ArgumentError do - @sliding_window.resize_to 0 + assert_raises(ArgumentError) do + @sliding_window.resize_to(0) end end diff --git a/test/simple_state_test.rb b/test/simple_state_test.rb index 0da9dd534..4a65b740f 100644 --- a/test/simple_state_test.rb +++ b/test/simple_state_test.rb @@ -1,4 +1,6 @@ -require 'test_helper' +# frozen_string_literal: true + +require "test_helper" class TestSimpleEnum < Minitest::Test def setup @@ -11,31 +13,31 @@ def teardown module StateTestCases def test_start_closed? - assert @state.closed? + assert_predicate(@state, :closed?) end def test_open @state.open! - assert @state.open? - assert_equal @state.value, :open + assert_predicate(@state, :open?) + assert_equal(:open, @state.value) end def test_close @state.close! - assert @state.closed? - assert_equal @state.value, :closed + assert_predicate(@state, :closed?) + assert_equal(:closed, @state.value) end def test_half_open @state.half_open! - assert @state.half_open? - assert_equal @state.value, :half_open + assert_predicate(@state, :half_open?) + assert_equal(:half_open, @state.value) end def test_reset @state.reset - assert @state.closed? - assert_equal @state.value, :closed + assert_predicate(@state, :closed?) + assert_equal(:closed, @state.value) end end diff --git a/test/test_helper.rb b/test/test_helper.rb index 2781c4828..9e3181250 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -1,63 +1,65 @@ -require 'rubygems' -require 'bundler/setup' +# frozen_string_literal: true -require 'minitest/autorun' -require 'mysql2' -require 'semian' -require 'semian/mysql2' -require 'semian/redis' -require 'semian/redis_client' -require 'toxiproxy' -require 'timecop' -require 'tempfile' -require 'fileutils' -require 'byebug' -require 'mocha' -require 'mocha/minitest' +require "rubygems" +require "bundler/setup" -require 'helpers/background_helper' -require 'helpers/circuit_breaker_helper' -require 'helpers/resource_helper' -require 'helpers/adapter_helper' -require 'helpers/mock_server.rb' +require "minitest/autorun" +require "mysql2" +require "semian" +require "semian/mysql2" +require "semian/redis" +require "semian/redis_client" +require "toxiproxy" +require "timecop" +require "tempfile" +require "fileutils" +require "byebug" +require "mocha" +require "mocha/minitest" -require 'config/semian_config' +require "helpers/background_helper" +require "helpers/circuit_breaker_helper" +require "helpers/resource_helper" +require "helpers/adapter_helper" +require "helpers/mock_server.rb" -BIND_ADDRESS = '0.0.0.0' +require "config/semian_config" + +BIND_ADDRESS = "0.0.0.0" Semian.logger = Logger.new(nil, Logger::FATAL) Toxiproxy.host = URI::HTTP.build( - host: SemianConfig['toxiproxy_upstream_host'], - port: SemianConfig['toxiproxy_upstream_port'], + host: SemianConfig["toxiproxy_upstream_host"], + port: SemianConfig["toxiproxy_upstream_port"], ) Toxiproxy.populate([ { - name: 'semian_test_mysql', - upstream: "#{SemianConfig['mysql_host']}:#{SemianConfig['mysql_port']}", - listen: "#{SemianConfig['toxiproxy_upstream_host']}:#{SemianConfig['mysql_toxiproxy_port']}", + name: "semian_test_mysql", + upstream: "#{SemianConfig["mysql_host"]}:#{SemianConfig["mysql_port"]}", + listen: "#{SemianConfig["toxiproxy_upstream_host"]}:#{SemianConfig["mysql_toxiproxy_port"]}", }, { - name: 'semian_test_redis', - upstream: "#{SemianConfig['redis_host']}:#{SemianConfig['redis_port']}", - listen: "#{SemianConfig['toxiproxy_upstream_host']}:#{SemianConfig['redis_toxiproxy_port']}", + name: "semian_test_redis", + upstream: "#{SemianConfig["redis_host"]}:#{SemianConfig["redis_port"]}", + listen: "#{SemianConfig["toxiproxy_upstream_host"]}:#{SemianConfig["redis_toxiproxy_port"]}", }, { - name: 'semian_test_net_http', - upstream: "#{SemianConfig['http_host']}:#{SemianConfig['http_port_service_a']}", - listen: "#{SemianConfig['toxiproxy_upstream_host']}:#{SemianConfig['http_toxiproxy_port']}", + name: "semian_test_net_http", + upstream: "#{SemianConfig["http_host"]}:#{SemianConfig["http_port_service_a"]}", + listen: "#{SemianConfig["toxiproxy_upstream_host"]}:#{SemianConfig["http_toxiproxy_port"]}", }, { - name: 'semian_test_grpc', - upstream: "#{SemianConfig['grpc_host']}:#{SemianConfig['grpc_port']}", - listen: "#{SemianConfig['toxiproxy_upstream_host']}:#{SemianConfig['grpc_toxiproxy_port']}", + name: "semian_test_grpc", + upstream: "#{SemianConfig["grpc_host"]}:#{SemianConfig["grpc_port"]}", + listen: "#{SemianConfig["toxiproxy_upstream_host"]}:#{SemianConfig["grpc_toxiproxy_port"]}", }, ]) servers = [] -servers << MockServer.start(hostname: BIND_ADDRESS, port: SemianConfig['http_port_service_a']) -servers << MockServer.start(hostname: BIND_ADDRESS, port: SemianConfig['http_port_service_b']) +servers << MockServer.start(hostname: BIND_ADDRESS, port: SemianConfig["http_port_service_a"]) +servers << MockServer.start(hostname: BIND_ADDRESS, port: SemianConfig["http_port_service_b"]) Minitest.after_run do servers.each(&:stop) @@ -69,7 +71,9 @@ def setup end end -class Minitest::Test - include CleanupHelper - include BackgroundHelper +module Minitest + class Test + include CleanupHelper + include BackgroundHelper + end end diff --git a/test/unprotected_resource_test.rb b/test/unprotected_resource_test.rb index e8321f300..c85c640d9 100644 --- a/test/unprotected_resource_test.rb +++ b/test/unprotected_resource_test.rb @@ -1,4 +1,6 @@ -require 'test_helper' +# frozen_string_literal: true + +require "test_helper" class UnprotectedResourceTest < Minitest::Test def setup @@ -7,11 +9,11 @@ def setup def test_interface_is_the_same diff = Semian::ProtectedResource.public_instance_methods - Semian::UnprotectedResource.public_instance_methods - assert_equal [], diff + assert_empty(diff) end def test_resource_name - assert_equal :foo, @resource.name + assert_equal(:foo, @resource.name) end def test_resource_tickets @@ -19,11 +21,11 @@ def test_resource_tickets end def test_resource_count - assert_equal 0, @resource.count + assert_equal(0, @resource.count) end def test_resource_semid - assert_equal 0, @resource.semid + assert_equal(0, @resource.semid) end def test_resource_reset @@ -39,7 +41,7 @@ def test_resource_acquire @resource.acquire do acquired = true end - assert acquired + assert(acquired) end def test_resource_acquire_with_timeout @@ -47,11 +49,11 @@ def test_resource_acquire_with_timeout @resource.acquire(timeout: 2) do acquired = true end - assert acquired + assert(acquired) end def test_request_is_allowed - assert @resource.request_allowed? + assert_predicate(@resource, :request_allowed?) end def test_mark_failed @@ -59,10 +61,10 @@ def test_mark_failed end def test_bulkhead - assert_nil @resource.bulkhead + assert_nil(@resource.bulkhead) end def test_circuit_breaker - assert_nil @resource.circuit_breaker + assert_nil(@resource.circuit_breaker) end end