diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index dc16edd9..3f7a2bd9 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -20,6 +20,10 @@ on: schedule: - cron: '30 21 * * 0' +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: analyze: name: Analyze @@ -42,26 +46,26 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v2 + uses: github/codeql-action/init@v3 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. # By default, queries listed here will override any specified in a config file. # Prefix the list here with "+" to use these queries and those in the config file. - + # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs # queries: security-extended,security-and-quality - + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@v2 + uses: github/codeql-action/autobuild@v3 # ℹī¸ Command-line programs to run using the OS shell. # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun - # If the Autobuild fails above, remove it and uncomment the following three lines. + # If the Autobuild fails above, remove it and uncomment the following three lines. # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. # - run: | @@ -69,4 +73,4 @@ jobs: # ./location_of_script_within_repo/buildscript.sh - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 + uses: github/codeql-action/analyze@v3 diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index cdb2283a..061eb198 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -1,5 +1,8 @@ -name: Test +name: Lint & Test on: push +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true jobs: # test_rails: # runs-on: ubuntu-latest @@ -31,12 +34,25 @@ jobs: # run: | # cd test/rails-tests/ # sh rails523.sh + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: ruby/setup-ruby@v1 + with: + ruby-version: '3.3' + bundler-cache: true + - name: Run rubocop + run: bundle exec rubocop + env: + RUBY_YJIT_ENABLE: true test: runs-on: ubuntu-latest + needs: lint strategy: fail-fast: false matrix: - ruby_version: ['2.6', '2.7', '3.0', '3.1'] + ruby_version: ['3.0', '3.1', '3.2', '3.3'] services: postgres: image: postgres @@ -50,7 +66,7 @@ jobs: ports: - 5432:5432 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Install postgresql-client run: | sudo apt-get update @@ -58,7 +74,7 @@ jobs: - uses: ruby/setup-ruby@v1 with: ruby-version: ${{ matrix.ruby_version }} - bundler-cache: true # runs 'bundle install' and caches installed gems automatically + bundler-cache: true - name: Run tests run: bundle exec rake env: @@ -68,4 +84,5 @@ jobs: DATABASE_URL: postgres://postgres:postgres@localhost/postgres QC_BENCHMARK: true QC_BENCHMARK_MAX_TIME_DEQUEUE: 60 - QC_BENCHMARK_MAX_TIME_ENQUEUE: 10 \ No newline at end of file + QC_BENCHMARK_MAX_TIME_ENQUEUE: 10 + RUBY_YJIT_ENABLE: true diff --git a/.rubocop.yml b/.rubocop.yml new file mode 100644 index 00000000..eab8218d --- /dev/null +++ b/.rubocop.yml @@ -0,0 +1,19 @@ +AllCops: + NewCops: enable + TargetRubyVersion: 3.0 + Exclude: + - 'vendor/**/*' +Layout/LineLength: + Enabled: false +Metrics/ClassLength: + Enabled: false +Metrics/MethodLength: + Enabled: false +Metrics/BlockLength: + Enabled: false +Naming/MethodParameterName: + Enabled: false +Metrics/AbcSize: + Enabled: false +Naming/VariableNumber: + Enabled: false diff --git a/.tool-versions b/.tool-versions new file mode 100644 index 00000000..3294aeda --- /dev/null +++ b/.tool-versions @@ -0,0 +1 @@ +ruby 3.3.0 diff --git a/Gemfile b/Gemfile index c9e5ba47..6ce57ac8 100644 --- a/Gemfile +++ b/Gemfile @@ -1,4 +1,6 @@ # frozen_string_literal: true + +# this, is dumb, but stops errors source 'https://rubygems.org' source 'https://rubygems.org' do @@ -6,6 +8,14 @@ source 'https://rubygems.org' do gemspec + group :development do + gem 'rubocop' + end + + group :development, :test do + gem 'activerecord', '>= 5.0.0', '< 6.1' + end + group :test do gem 'minitest', '~> 5.8' gem 'minitest-reporters' diff --git a/README.md b/README.md index bbbe1ed4..85c9571d 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ A major benefit is the ability to enqueue inside transactions, ensuring things a ### Requirements For this version, the requirements are as follows: -* Ruby 2.6, 2.7, 3.0, 3.1 - i.e. currently supported Ruby versions +* Ruby 3.0, 3.1, 3.2, 3.3 - i.e. currently supported non-EOL Ruby versions * Postgres ~> 9.6 * Rubygem: pg ~> 1.1 @@ -212,7 +212,7 @@ bundle exec rake db:migrate ``` #### Database connection -Starting with with queue_classic 3.1, Rails is automatically detected and its connection is used. If you don't want to use the automatic database connection, set this environment variable to false: `export QC_RAILS_DATABASE=false`. +Starting with with queue_classic 3.1, Rails is automatically detected and its connection is used. If you don't want to use the automatic database connection, set this environment variable to false: `export QC_RAILS_DATABASE=false`. > **Note:** If you do not share the connection, you cannot enqueue in the same transaction as whatever you're doing in Rails. diff --git a/Rakefile b/Rakefile index 2c2ddcd3..592f9205 100644 --- a/Rakefile +++ b/Rakefile @@ -1,13 +1,13 @@ # frozen_string_literal: true -$:.unshift("lib") +$LOAD_PATH.unshift('lib') -require "bundler/gem_tasks" -require "rake/testtask" -require "./lib/queue_classic" -require "./lib/queue_classic/tasks" +require 'bundler/gem_tasks' +require 'rake/testtask' +require './lib/queue_classic' +require './lib/queue_classic/tasks' -task :default => ['test'] +task default: ['test'] Rake::TestTask.new do |t| t.libs << 'test' t.test_files = FileList['test/**/*_test.rb'] diff --git a/lib/generators/queue_classic/install_generator.rb b/lib/generators/queue_classic/install_generator.rb index b8d0b6c0..1591cc6d 100644 --- a/lib/generators/queue_classic/install_generator.rb +++ b/lib/generators/queue_classic/install_generator.rb @@ -5,11 +5,12 @@ require 'active_record' module QC + # Install generator to create migration files for rails class InstallGenerator < Rails::Generators::Base include Rails::Generators::Migration - namespace "queue_classic:install" - self.source_paths << File.join(File.dirname(__FILE__), 'templates') + namespace 'queue_classic:install' + source_paths << File.join(File.dirname(__FILE__), 'templates') desc 'Generates (but does not run) a migration to add a queue_classic table.' def self.next_migration_number(dirname) @@ -18,25 +19,11 @@ def self.next_migration_number(dirname) end def create_migration_file - if self.class.migration_exists?('db/migrate', 'add_queue_classic').nil? - migration_template 'add_queue_classic.rb', 'db/migrate/add_queue_classic.rb' - end - - if self.class.migration_exists?('db/migrate', 'update_queue_classic_3_0_0').nil? - migration_template 'update_queue_classic_3_0_0.rb', 'db/migrate/update_queue_classic_3_0_0.rb' - end - - if self.class.migration_exists?('db/migrate', 'update_queue_classic_3_0_2').nil? - migration_template 'update_queue_classic_3_0_2.rb', 'db/migrate/update_queue_classic_3_0_2.rb' - end - - if self.class.migration_exists?('db/migrate', 'update_queue_classic_3_1_0').nil? - migration_template 'update_queue_classic_3_1_0.rb', 'db/migrate/update_queue_classic_3_1_0.rb' - end - - if self.class.migration_exists?('db/migrate', 'update_queue_classic_4_0_0').nil? - migration_template 'update_queue_classic_4_0_0.rb', 'db/migrate/update_queue_classic_4_0_0.rb' - end + migration_template 'add_queue_classic.rb', 'db/migrate/add_queue_classic.rb' if self.class.migration_exists?('db/migrate', 'add_queue_classic').nil? + migration_template 'update_queue_classic_3_0_0.rb', 'db/migrate/update_queue_classic_3_0_0.rb' if self.class.migration_exists?('db/migrate', 'update_queue_classic_3_0_0').nil? + migration_template 'update_queue_classic_3_0_2.rb', 'db/migrate/update_queue_classic_3_0_2.rb' if self.class.migration_exists?('db/migrate', 'update_queue_classic_3_0_2').nil? + migration_template 'update_queue_classic_3_1_0.rb', 'db/migrate/update_queue_classic_3_1_0.rb' if self.class.migration_exists?('db/migrate', 'update_queue_classic_3_1_0').nil? + migration_template 'update_queue_classic_4_0_0.rb', 'db/migrate/update_queue_classic_4_0_0.rb' if self.class.migration_exists?('db/migrate', 'update_queue_classic_4_0_0').nil? end end end diff --git a/lib/generators/queue_classic/templates/add_queue_classic.rb b/lib/generators/queue_classic/templates/add_queue_classic.rb index 48c40acc..6877fa59 100644 --- a/lib/generators/queue_classic/templates/add_queue_classic.rb +++ b/lib/generators/queue_classic/templates/add_queue_classic.rb @@ -1,5 +1,6 @@ # frozen_string_literal: true +# add QC class AddQueueClassic < ActiveRecord::Migration[4.2] def self.up QC::Setup.create diff --git a/lib/generators/queue_classic/templates/update_queue_classic_3_0_0.rb b/lib/generators/queue_classic/templates/update_queue_classic_3_0_0.rb index a7c80b03..5fd61c19 100644 --- a/lib/generators/queue_classic/templates/update_queue_classic_3_0_0.rb +++ b/lib/generators/queue_classic/templates/update_queue_classic_3_0_0.rb @@ -1,5 +1,6 @@ # frozen_string_literal: true +# update QC class UpdateQueueClassic300 < ActiveRecord::Migration[4.2] def self.up QC::Setup.update_to_3_0_0 diff --git a/lib/generators/queue_classic/templates/update_queue_classic_3_0_2.rb b/lib/generators/queue_classic/templates/update_queue_classic_3_0_2.rb index bace9c96..50b1004f 100644 --- a/lib/generators/queue_classic/templates/update_queue_classic_3_0_2.rb +++ b/lib/generators/queue_classic/templates/update_queue_classic_3_0_2.rb @@ -1,5 +1,6 @@ # frozen_string_literal: true +# update QC class UpdateQueueClassic302 < ActiveRecord::Migration[4.2] def self.up QC::Setup.update_to_3_0_0 diff --git a/lib/generators/queue_classic/templates/update_queue_classic_3_1_0.rb b/lib/generators/queue_classic/templates/update_queue_classic_3_1_0.rb index b940ba0a..f99484d5 100644 --- a/lib/generators/queue_classic/templates/update_queue_classic_3_1_0.rb +++ b/lib/generators/queue_classic/templates/update_queue_classic_3_1_0.rb @@ -1,5 +1,6 @@ # frozen_string_literal: true +# update QC class UpdateQueueClassic310 < ActiveRecord::Migration[4.2] def self.up QC::Setup.update_to_3_1_0 diff --git a/lib/generators/queue_classic/templates/update_queue_classic_4_0_0.rb b/lib/generators/queue_classic/templates/update_queue_classic_4_0_0.rb index e3d429f6..f0d47bb2 100644 --- a/lib/generators/queue_classic/templates/update_queue_classic_4_0_0.rb +++ b/lib/generators/queue_classic/templates/update_queue_classic_4_0_0.rb @@ -1,5 +1,6 @@ # frozen_string_literal: true +# update QC class UpdateQueueClassic400 < ActiveRecord::Migration[4.2] def self.up QC::Setup.update_to_4_0_0 diff --git a/lib/queue_classic.rb b/lib/queue_classic.rb index 687a142d..ee1303a6 100644 --- a/lib/queue_classic.rb +++ b/lib/queue_classic.rb @@ -1,7 +1,8 @@ # frozen_string_literal: true -require_relative "queue_classic/config" +require_relative 'queue_classic/config' +# QC module QC extend QC::Config @@ -9,21 +10,21 @@ module QC # They should no longer be used. Prefer the corresponding methods. # See +QC::Config+ for more details. DEPRECATED_CONSTANTS = { - :APP_NAME => :app_name, - :WAIT_TIME => :wait_time, - :TABLE_NAME => :table_name, - :QUEUE => :queue, - :QUEUES => :queues, - :TOP_BOUND => :top_bound, - :FORK_WORKER => :fork_worker?, - } + APP_NAME: :app_name, + WAIT_TIME: :wait_time, + TABLE_NAME: :table_name, + QUEUE: :queue, + QUEUES: :queues, + TOP_BOUND: :top_bound, + FORK_WORKER: :fork_worker? + }.freeze def self.const_missing(const_name) if DEPRECATED_CONSTANTS.key? const_name config_method = DEPRECATED_CONSTANTS[const_name] - $stderr.puts <<-MSG -The constant QC::#{const_name} is deprecated and will be removed in the future. -Please use the method QC.#{config_method} instead. + warn <<~MSG + The constant QC::#{const_name} is deprecated and will be removed in the future. + Please use the method QC.#{config_method} instead. MSG QC.public_send config_method else @@ -33,20 +34,20 @@ def self.const_missing(const_name) # Defer method calls on the QC module to the # default queue. This facilitates QC.enqueue() - def self.method_missing(sym, *args, &block) + def self.method_missing(sym, ...) if default_queue.respond_to? sym - default_queue.public_send(sym, *args, &block) + default_queue.public_send(sym, ...) else super end end # Ensure QC.respond_to?(:enqueue) equals true (ruby 1.9 only) - def self.respond_to_missing?(method_name, include_private = false) + def self.respond_to_missing?(method_name, _include_private = false) default_queue.respond_to?(method_name) end - def self.has_connection? + def self.has_connection? # rubocop:disable Naming/PredicateName !default_conn_adapter.nil? end @@ -62,33 +63,33 @@ def self.log_yield(data) t0 = Time.now begin yield - rescue => e - log({:at => "error", :error => e.inspect}.merge(data)) + rescue StandardError => e + log({ at: 'error', error: e.inspect }.merge(data)) raise ensure - t = Integer((Time.now - t0)*1000) - log(data.merge(:elapsed => t)) unless e + t = Integer((Time.now - t0) * 1000) + log(data.merge(elapsed: t)) unless e end end def self.log(data) result = nil - data = {:lib => "queue-classic"}.merge(data) + data = { lib: 'queue-classic' }.merge(data) if block_given? result = yield - data.merge(:elapsed => Integer((Time.now - t0)*1000)) + data.merge(elapsed: Integer((Time.now - t0) * 1000)) end - data.reduce(out=String.new) do |s, tup| - s << [tup.first, tup.last].join("=") << " " + data.reduce(out = String.new) do |s, tup| + s << [tup.first, tup.last].join('=') << ' ' end - puts(out) if ENV["DEBUG"] - return result + puts(out) if ENV['DEBUG'] + result end def self.measure(data) - if ENV['QC_MEASURE'] - $stdout.puts("measure#qc.#{data}") - end + return unless ENV['QC_MEASURE'] + + $stdout.puts("measure#qc.#{data}") end # This will unlock all jobs any postgres' PID that is not existing anymore @@ -104,12 +105,13 @@ class << self def rails_connection_sharing_enabled? enabled = ENV.fetch('QC_RAILS_DATABASE', 'true') != 'false' return false unless enabled - return Object.const_defined?("ActiveRecord") && ActiveRecord::Base.respond_to?("connection") + + Object.const_defined?('ActiveRecord') && ActiveRecord::Base.respond_to?('connection') end end end -require_relative "queue_classic/queue" -require_relative "queue_classic/worker" -require_relative "queue_classic/setup" -require_relative "queue_classic/railtie" if defined?(Rails) +require_relative 'queue_classic/queue' +require_relative 'queue_classic/worker' +require_relative 'queue_classic/setup' +require_relative 'queue_classic/railtie' if defined?(Rails) diff --git a/lib/queue_classic/config.rb b/lib/queue_classic/config.rb index 1cb8a6c2..68b6c2a6 100644 --- a/lib/queue_classic/config.rb +++ b/lib/queue_classic/config.rb @@ -1,17 +1,18 @@ # frozen_string_literal: true module QC + # QC Configuration module Config # You can use the APP_NAME to query for # postgres related process information in the # pg_stat_activity table. def app_name - @app_name ||= ENV["QC_APP_NAME"] || "queue_classic" + @app_name ||= ENV['QC_APP_NAME'] || 'queue_classic' end # Number of seconds to block on the listen chanel for new jobs. def wait_time - @wait_time ||= (ENV["QC_LISTEN_TIME"] || 5).to_i + @wait_time ||= (ENV['QC_LISTEN_TIME'] || 5).to_i end # Why do you want to change the table name? @@ -20,11 +21,11 @@ def wait_time # need to update the PL/pgSQL lock_head() function. # Come on. Don't do it.... Just stick with the default. def table_name - @table_name ||= "queue_classic_jobs" + @table_name ||= 'queue_classic_jobs' end def queue - @queue = ENV["QUEUE"] || "default" + @queue = ENV['QUEUE'] || 'default' end # The default queue used by `QC.enqueue`. @@ -40,13 +41,13 @@ def default_queue=(queue) # notes the queue. You can point your workers # at different queues. def queues - @queues ||= (ENV["QUEUES"] && ENV["QUEUES"].split(",").map(&:strip)) || [] + @queues ||= (ENV.fetch('QUEUES', nil) && ENV['QUEUES'].split(',').map(&:strip)) || [] end # Set this to 1 for strict FIFO. # There is nothing special about 9.... def top_bound - @top_bound ||= (ENV["QC_TOP_BOUND"] || 9).to_i + @top_bound ||= (ENV['QC_TOP_BOUND'] || 9).to_i end # Set this variable if you wish for @@ -55,18 +56,21 @@ def top_bound # any database connections. See the worker # for more details. def fork_worker? - @fork_worker ||= (!ENV["QC_FORK_WORKER"].nil?) + @fork_worker ||= !ENV['QC_FORK_WORKER'].nil? end # The worker class instantiated by QC's rake tasks. def default_worker_class - @worker_class ||= (ENV["QC_DEFAULT_WORKER_CLASS"] && Kernel.const_get(ENV["QC_DEFAULT_WORKER_CLASS"]) || - QC::Worker) - + @default_worker_class ||= begin + class_name = ENV.fetch('QC_DEFAULT_WORKER_CLASS', nil) + class_name ? Kernel.const_get(class_name) : QC::Worker + rescue NameError + QC::Worker + end end def default_worker_class=(worker_class) - @worker_class = worker_class + @default_worker_class = worker_class end # reset memoized configuration diff --git a/lib/queue_classic/conn_adapter.rb b/lib/queue_classic/conn_adapter.rb index 4e434120..422b5e77 100644 --- a/lib/queue_classic/conn_adapter.rb +++ b/lib/queue_classic/conn_adapter.rb @@ -4,11 +4,11 @@ require 'pg' module QC + # This class is responsible for managing the connection to the database. class ConnAdapter - - def initialize(args={}) + def initialize(args = {}) @active_record_connection_share = args[:active_record_connection_share] - @_connection = args[:connection] + @connection = args[:connection] @mutex = Mutex.new end @@ -16,21 +16,21 @@ def connection if @active_record_connection_share && Object.const_defined?('ActiveRecord') ActiveRecord::Base.connection.raw_connection else - @_connection ||= establish_new + @connection ||= establish_new end end def execute(stmt, *params) @mutex.synchronize do - QC.log(:at => "exec_sql", :sql => stmt.inspect) + QC.log(at: 'exec_sql', sql: stmt.inspect) begin params = nil if params.empty? r = connection.exec(stmt, params) result = [] - r.each {|t| result << t} + r.each { |t| result << t } result.length > 1 ? result : result.pop rescue PG::Error => e - QC.log(:error => e.inspect) + QC.log(error: e.inspect) connection.reset raise end @@ -39,10 +39,10 @@ def execute(stmt, *params) def wait(time, *channels) @mutex.synchronize do - listen_cmds = channels.map {|c| 'LISTEN "' + c.to_s + '"'} + listen_cmds = channels.map { |c| "LISTEN \"#{c}\"" } connection.exec(listen_cmds.join(';')) wait_for_notify(time) - unlisten_cmds = channels.map {|c| 'UNLISTEN "' + c.to_s + '"'} + unlisten_cmds = channels.map { |c| "UNLISTEN \"#{c}\"" } connection.exec(unlisten_cmds.join(';')) drain_notify end @@ -50,51 +50,44 @@ def wait(time, *channels) def disconnect @mutex.synchronize do - begin - connection.close - rescue => e - QC.log(:at => 'disconnect', :error => e.message) - end + connection.close + rescue StandardError => e + QC.log(at: 'disconnect', error: e.message) end end def server_version @server_version ||= begin - version = execute("SHOW server_version_num;")["server_version_num"] - version && version.to_i - end + version = execute('SHOW server_version_num;')['server_version_num'] + version&.to_i + end end private def wait_for_notify(t) - Array.new.tap do |msgs| - connection.wait_for_notify(t) {|event, pid, msg| msgs << msg} + [].tap do |msgs| + connection.wait_for_notify(t) { |_event, _pid, msg| msgs << msg } end end def drain_notify - until connection.notifies.nil? - QC.log(:at => "drain_notifications") - end + QC.log(at: 'drain_notifications') until connection.notifies.nil? end def validate!(c) return c if c.is_a?(PG::Connection) + err = "connection must be an instance of PG::Connection, but was #{c.class}" raise(ArgumentError, err) end def establish_new - QC.log(:at => "establish_conn") + QC.log(at: 'establish_conn') conn = PG.connect(*normalize_db_url(db_url)) - if conn.status != PG::CONNECTION_OK - QC.log(:error => conn.error) - end + QC.log(error: conn.error) if conn.status != PG::CONNECTION_OK - if conn.server_version < 90600 - raise "This version of Queue Classic does not support Postgres older than 9.6 (90600). This version is #{conn.server_version}. If you need that support, please use an older version." - end + raise "This version of Queue Classic does not support Postgres older than 9.6 (90600). This version is #{conn.server_version}. If you need that support, please use an older version." if conn.server_version < 90_600 conn.exec("SET application_name = '#{QC.app_name}'") conn @@ -105,20 +98,21 @@ def normalize_db_url(url) host = host.gsub(/%2F/i, '/') if host [ - host, # host or percent-encoded socket path - url.port || 5432, - nil, nil, #opts, tty - url.path.gsub("/",""), # database name - url.user, - url.password + host, # host or percent-encoded socket path + url.port || 5432, + nil, nil, # opts, tty + url.path.gsub('/', ''), # database name + url.user, + url.password ] end def db_url return @db_url if defined?(@db_url) && @db_url - url = ENV["QC_DATABASE_URL"] || - ENV["DATABASE_URL"] || - raise(ArgumentError, "missing QC_DATABASE_URL or DATABASE_URL") + + url = ENV['QC_DATABASE_URL'] || + ENV['DATABASE_URL'] || + raise(ArgumentError, 'missing QC_DATABASE_URL or DATABASE_URL') @db_url = URI.parse(url) end end diff --git a/lib/queue_classic/queue.rb b/lib/queue_classic/queue.rb index 16862527..69fe424a 100644 --- a/lib/queue_classic/queue.rb +++ b/lib/queue_classic/queue.rb @@ -9,17 +9,15 @@ module QC class Queue attr_reader :name, :top_bound - def initialize(name, top_bound=nil) + def initialize(name, top_bound = nil) @name = name @top_bound = top_bound || QC.top_bound end - def conn_adapter=(a) - @adapter = a - end + attr_writer :conn_adapter def conn_adapter - @adapter ||= QC.default_conn_adapter + @conn_adapter ||= QC.default_conn_adapter end # enqueue(m,a) inserts a row into the jobs table and trigger a notification. @@ -38,17 +36,15 @@ def conn_adapter # `'hello', 'world'`. # This method returns a hash with the id of the enqueued job. def enqueue(method, *args) - QC.log_yield(:measure => 'queue.enqueue') do + QC.log_yield(measure: 'queue.enqueue') do s = "INSERT INTO #{QC.table_name} (q_name, method, args) VALUES ($1, $2, $3) RETURNING id" begin retries ||= 0 conn_adapter.execute(s, name, method, JSON.dump(args)) rescue PG::Error - if (retries += 1) < 2 - retry - else - raise - end + raise unless (retries += 1) < 2 + + retry end end end @@ -71,7 +67,7 @@ def enqueue_at(timestamp, method, *args) # method. # This method returns a hash with the id of the enqueued job. def enqueue_in(seconds, method, *args) - QC.log_yield(:measure => 'queue.enqueue') do + QC.log_yield(measure: 'queue.enqueue') do s = "INSERT INTO #{QC.table_name} (q_name, method, args, scheduled_at) VALUES ($1, $2, $3, now() + interval '#{seconds.to_i} seconds') RETURNING id" @@ -79,17 +75,15 @@ def enqueue_in(seconds, method, *args) retries ||= 0 conn_adapter.execute(s, name, method, JSON.dump(args)) rescue PG::Error - if (retries += 1) < 2 - retry - else - raise - end + raise unless (retries += 1) < 2 + + retry end end end def lock - QC.log_yield(:measure => 'queue.lock') do + QC.log_yield(measure: 'queue.lock') do s = <<~SQL WITH selected_job AS ( SELECT id @@ -109,14 +103,14 @@ def lock RETURNING * SQL - if r = conn_adapter.execute(s, name) + if (r = conn_adapter.execute(s, name)) {}.tap do |job| - job[:id] = r["id"] - job[:q_name] = r["q_name"] - job[:method] = r["method"] - job[:args] = JSON.parse(r["args"]) - if r["scheduled_at"] - job[:scheduled_at] = r["scheduled_at"].kind_of?(Time) ? r["scheduled_at"] : Time.parse(r["scheduled_at"]) + job[:id] = r['id'] + job[:q_name] = r['q_name'] + job[:method] = r['method'] + job[:args] = JSON.parse(r['args']) + if r['scheduled_at'] + job[:scheduled_at] = r['scheduled_at'].is_a?(Time) ? r['scheduled_at'] : Time.parse(r['scheduled_at']) ttl = Integer((Time.now - job[:scheduled_at]) * 1000) QC.measure("time-to-lock=#{ttl}ms source=#{name}") end @@ -126,20 +120,20 @@ def lock end def unlock(id) - QC.log_yield(:measure => 'queue.unlock') do + QC.log_yield(measure: 'queue.unlock') do s = "UPDATE #{QC.table_name} SET locked_at = NULL WHERE id = $1" conn_adapter.execute(s, id) end end def delete(id) - QC.log_yield(:measure => 'queue.delete') do + QC.log_yield(measure: 'queue.delete') do conn_adapter.execute("DELETE FROM #{QC.table_name} WHERE id = $1", id) end end def delete_all - QC.log_yield(:measure => 'queue.delete_all') do + QC.log_yield(measure: 'queue.delete_all') do s = "DELETE FROM #{QC.table_name} WHERE q_name = $1" conn_adapter.execute(s, name) end @@ -153,19 +147,22 @@ def count # Count the number of jobs in a specific queue, except ones scheduled in the future def count_ready - _count('queue.count_scheduled', "SELECT COUNT(*) FROM #{QC.table_name} WHERE q_name = $1 AND scheduled_at <= now()") + _count('queue.count_scheduled', + "SELECT COUNT(*) FROM #{QC.table_name} WHERE q_name = $1 AND scheduled_at <= now()") end # Count the number of jobs in a specific queue scheduled in the future def count_scheduled - _count('queue.count_scheduled', "SELECT COUNT(*) FROM #{QC.table_name} WHERE q_name = $1 AND scheduled_at > now()") + _count('queue.count_scheduled', + "SELECT COUNT(*) FROM #{QC.table_name} WHERE q_name = $1 AND scheduled_at > now()") end private + def _count(metric_name, sql) QC.log_yield(measure: metric_name) do r = conn_adapter.execute(sql, name) - r["count"].to_i + r['count'].to_i end end end diff --git a/lib/queue_classic/railtie.rb b/lib/queue_classic/railtie.rb index 46243f68..3b3bf792 100644 --- a/lib/queue_classic/railtie.rb +++ b/lib/queue_classic/railtie.rb @@ -3,6 +3,7 @@ require 'rails/railtie' module QC + # Railtie integrates queue_classic with Rails applications. class Railtie < ::Rails::Railtie rake_tasks do load 'queue_classic/tasks.rb' diff --git a/lib/queue_classic/setup.rb b/lib/queue_classic/setup.rb index e25d2a35..835f25a9 100644 --- a/lib/queue_classic/setup.rb +++ b/lib/queue_classic/setup.rb @@ -1,33 +1,34 @@ # frozen_string_literal: true module QC + # Setup is a module that provides methods to create, update and drop the queue_classic tables module Setup - Root = File.expand_path("../..", File.dirname(__FILE__)) - SqlFunctions = File.join(Root, "/sql/ddl.sql") - CreateTable = File.join(Root, "/sql/create_table.sql") - DropSqlFunctions = File.join(Root, "/sql/drop_ddl.sql") - UpgradeTo_3_0_0 = File.join(Root, "/sql/update_to_3_0_0.sql") - DowngradeFrom_3_0_0 = File.join(Root, "/sql/downgrade_from_3_0_0.sql") - UpgradeTo_3_1_0 = File.join(Root, "/sql/update_to_3_1_0.sql") - DowngradeFrom_3_1_0 = File.join(Root, "/sql/downgrade_from_3_1_0.sql") - UpgradeTo_4_0_0 = File.join(Root, "/sql/update_to_4_0_0.sql") - DowngradeFrom_4_0_0 = File.join(Root, "/sql/downgrade_from_4_0_0.sql") + Root = File.expand_path('../..', File.dirname(__FILE__)) + SqlFunctions = File.join(Root, '/sql/ddl.sql') + CreateTable = File.join(Root, '/sql/create_table.sql') + DropSqlFunctions = File.join(Root, '/sql/drop_ddl.sql') + UpgradeTo_3_0_0 = File.join(Root, '/sql/update_to_3_0_0.sql') + DowngradeFrom_3_0_0 = File.join(Root, '/sql/downgrade_from_3_0_0.sql') + UpgradeTo_3_1_0 = File.join(Root, '/sql/update_to_3_1_0.sql') + DowngradeFrom_3_1_0 = File.join(Root, '/sql/downgrade_from_3_1_0.sql') + UpgradeTo_4_0_0 = File.join(Root, '/sql/update_to_4_0_0.sql') + DowngradeFrom_4_0_0 = File.join(Root, '/sql/downgrade_from_4_0_0.sql') - def self.create(c = QC::default_conn_adapter.connection) + def self.create(c = QC.default_conn_adapter.connection) conn = QC::ConnAdapter.new(connection: c) conn.execute(File.read(CreateTable)) conn.execute(File.read(SqlFunctions)) - conn.disconnect if c.nil? #Don't close a conn we didn't create. + conn.disconnect if c.nil? # Don't close a conn we didn't create. end - def self.drop(c = QC::default_conn_adapter.connection) + def self.drop(c = QC.default_conn_adapter.connection) conn = QC::ConnAdapter.new(connection: c) - conn.execute("DROP TABLE IF EXISTS queue_classic_jobs CASCADE") + conn.execute('DROP TABLE IF EXISTS queue_classic_jobs CASCADE') conn.execute(File.read(DropSqlFunctions)) - conn.disconnect if c.nil? #Don't close a conn we didn't create. + conn.disconnect if c.nil? # Don't close a conn we didn't create. end - def self.update(c = QC::default_conn_adapter.connection) + def self.update(c = QC.default_conn_adapter.connection) conn = QC::ConnAdapter.new(connection: c) conn.execute(File.read(UpgradeTo_3_0_0)) conn.execute(File.read(UpgradeTo_3_1_0)) @@ -36,38 +37,38 @@ def self.update(c = QC::default_conn_adapter.connection) conn.execute(File.read(SqlFunctions)) end - def self.update_to_3_0_0(c = QC::default_conn_adapter.connection) + def self.update_to_3_0_0(c = QC.default_conn_adapter.connection) conn = QC::ConnAdapter.new(connection: c) conn.execute(File.read(UpgradeTo_3_0_0)) conn.execute(File.read(DropSqlFunctions)) conn.execute(File.read(SqlFunctions)) end - def self.downgrade_from_3_0_0(c = QC::default_conn_adapter.connection) + def self.downgrade_from_3_0_0(c = QC.default_conn_adapter.connection) conn = QC::ConnAdapter.new(connection: c) conn.execute(File.read(DowngradeFrom_3_0_0)) end - def self.update_to_3_1_0(c = QC::default_conn_adapter.connection) + def self.update_to_3_1_0(c = QC.default_conn_adapter.connection) conn = QC::ConnAdapter.new(connection: c) conn.execute(File.read(UpgradeTo_3_1_0)) conn.execute(File.read(DropSqlFunctions)) conn.execute(File.read(SqlFunctions)) end - def self.downgrade_from_3_1_0(c = QC::default_conn_adapter.connection) + def self.downgrade_from_3_1_0(c = QC.default_conn_adapter.connection) conn = QC::ConnAdapter.new(connection: c) conn.execute(File.read(DowngradeFrom_3_1_0)) end - def self.update_to_4_0_0(c = QC::default_conn_adapter.connection) + def self.update_to_4_0_0(c = QC.default_conn_adapter.connection) conn = QC::ConnAdapter.new(connection: c) conn.execute(File.read(UpgradeTo_4_0_0)) conn.execute(File.read(DropSqlFunctions)) conn.execute(File.read(SqlFunctions)) end - def self.downgrade_from_4_0_0(c = QC::default_conn_adapter.connection) + def self.downgrade_from_4_0_0(c = QC.default_conn_adapter.connection) conn = QC::ConnAdapter.new(connection: c) conn.execute(File.read(DowngradeFrom_4_0_0)) end diff --git a/lib/queue_classic/tasks.rb b/lib/queue_classic/tasks.rb index d3aa5a23..a397b80a 100644 --- a/lib/queue_classic/tasks.rb +++ b/lib/queue_classic/tasks.rb @@ -3,46 +3,46 @@ task :environment namespace :jobs do - desc "Alias for qc:work" - task :work => "qc:work" + desc 'Alias for qc:work' + task work: 'qc:work' end namespace :qc do - desc "Start a new worker for the (default or $QUEUE / $QUEUES) queue" - task :work => :environment do + desc 'Start a new worker for the (default or $QUEUE / $QUEUES) queue' + task work: :environment do @worker = QC.default_worker_class.new trap('INT') do - $stderr.puts("Received INT. Shutting down.") - abort("Worker has stopped running. Exit.") unless @worker.running + warn('Received INT. Shutting down.') + abort('Worker has stopped running. Exit.') unless @worker.running @worker.stop end trap('TERM') do - $stderr.puts("Received Term. Shutting down.") + warn('Received Term. Shutting down.') @worker.stop end @worker.start end - desc "Returns the number of jobs in the (default or $QUEUE / $QUEUES) queue" - task :count => :environment do + desc 'Returns the number of jobs in the (default or $QUEUE / $QUEUES) queue' + task count: :environment do puts QC.default_queue.count end - desc "Setup queue_classic tables and functions in database" - task :create => :environment do + desc 'Setup queue_classic tables and functions in database' + task create: :environment do QC::Setup.create end - desc "Remove queue_classic tables and functions from database." - task :drop => :environment do + desc 'Remove queue_classic tables and functions from database.' + task drop: :environment do QC::Setup.drop end - desc "Update queue_classic tables and functions in database" - task :update => :environment do + desc 'Update queue_classic tables and functions in database' + task update: :environment do QC::Setup.update end end diff --git a/lib/queue_classic/version.rb b/lib/queue_classic/version.rb index 615176ae..f6104161 100644 --- a/lib/queue_classic/version.rb +++ b/lib/queue_classic/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module QC - VERSION = "4.0.0" + VERSION = '4.0.0' end diff --git a/lib/queue_classic/worker.rb b/lib/queue_classic/worker.rb index 9a4b639d..10522849 100644 --- a/lib/queue_classic/worker.rb +++ b/lib/queue_classic/worker.rb @@ -1,13 +1,11 @@ # frozen_string_literal: true -# -*- coding: utf-8 -*- require_relative 'queue' require_relative 'conn_adapter' module QC # A Worker object can process jobs from one or many queues. class Worker - attr_accessor :queues, :running # Creates a new worker but does not start the worker. See Worker#start. @@ -18,21 +16,21 @@ class Worker # q_name:: Name of a single queue to process. # q_names:: Names of queues to process. Will process left to right. # top_bound:: Offset to the head of the queue. 1 == strict FIFO. - def initialize(args={}) + def initialize(args = {}) @fork_worker = args[:fork_worker] || QC.fork_worker? @wait_interval = args[:wait_interval] || QC.wait_time - if args[:connection] - @conn_adapter = ConnAdapter.new(connection: args[:connection]) - else - @conn_adapter = QC.default_conn_adapter - end + @conn_adapter = if args[:connection] + ConnAdapter.new(connection: args[:connection]) + else + QC.default_conn_adapter + end @queues = setup_queues(@conn_adapter, - (args[:q_name] || QC.queue), - (args[:q_names] || QC.queues), - (args[:top_bound] || QC.top_bound)) - log(args.merge(:at => "worker_initialized")) + (args[:q_name] || QC.queue), + (args[:q_names] || QC.queues), + (args[:top_bound] || QC.top_bound)) + log(args.merge(at: 'worker_initialized')) @running = true end @@ -44,9 +42,7 @@ def initialize(args={}) def start QC.unlock_jobs_of_dead_workers - while @running - @fork_worker ? fork_and_work : work - end + @fork_worker ? fork_and_work : work while @running end # Signals the worker to stop taking new work. @@ -63,8 +59,11 @@ def stop # Calls Worker#work but after the current process is forked. # The parent process will wait on the child process to exit. def fork_and_work - cpid = fork {setup_child; work} - log(:at => :fork, :pid => cpid) + cpid = fork do + setup_child + work + end + log(at: :fork, pid: cpid) Process.wait(cpid) end @@ -72,10 +71,10 @@ def fork_and_work # it will process the job. def work queue, job = lock_job - if queue && job - QC.log_yield(:at => "work", :job => job[:id]) do - process(queue, job) - end + return unless queue && job + + QC.log_yield(at: 'work', job: job[:id]) do + process(queue, job) end end @@ -87,15 +86,15 @@ def work # job's row. It is the caller's responsibility to delete the job row # from the table when the job is complete. def lock_job - log(:at => "lock_job") + log(at: 'lock_job') job = nil while @running @queues.each do |queue| - if job = queue.lock + if (job = queue.lock) return [queue, job] end end - @conn_adapter.wait(@wait_interval, *@queues.map {|q| q.name}) + @conn_adapter.wait(@wait_interval, *@queues.map(&:name)) end end @@ -121,9 +120,7 @@ def process(queue, job) handle_failure(job, e) finished = true ensure - if !finished - queue.unlock(job[:id]) - end + queue.unlock(job[:id]) unless finished ttp = Integer((Time.now - start) * 1000) QC.measure("time-to-process=#{ttp} source=#{queue.name}") end @@ -135,7 +132,7 @@ def process(queue, job) def call(job) args = job[:args] receiver_str, _, message = job[:method].rpartition('.') - receiver = eval(receiver_str) + receiver = eval(receiver_str) # rubocop:disable Security/Eval receiver.send(message, *args) end @@ -145,15 +142,15 @@ def handle_success(queue, job) # This method will be called when a StandardError, ScriptError or # NoMemoryError is raised during the execution of the job. - def handle_failure(job,e) - $stderr.puts("count#qc.job-error=1 job=#{job} error=#{e.inspect} at=#{e.backtrace.first}") + def handle_failure(job, e) + warn("count#qc.job-error=1 job=#{job} error=#{e.inspect} at=#{e.backtrace.first}") end # This method should be overriden if # your worker is forking and you need to # re-establish database connections def setup_child - log(:at => "setup_child") + log(at: 'setup_child') end def log(data) @@ -163,13 +160,12 @@ def log(data) private def setup_queues(adapter, queue, queues, top_bound) - names = queues.length > 0 ? queues : [queue] + names = queues.length.positive? ? queues : [queue] names.map do |name| QC::Queue.new(name, top_bound).tap do |q| q.conn_adapter = adapter end end end - end end diff --git a/queue_classic.gemspec b/queue_classic.gemspec index e3620d6a..4ea9fe1d 100644 --- a/queue_classic.gemspec +++ b/queue_classic.gemspec @@ -1,25 +1,30 @@ -# coding: utf-8 -lib = File.expand_path('../lib', __FILE__) +# frozen_string_literal: true + +lib = File.expand_path('lib', __dir__) $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) require 'queue_classic/version' Gem::Specification.new do |spec| - spec.name = "queue_classic" - spec.email = "r@32k.io" + spec.name = 'queue_classic' + spec.email = 'r@32k.io' spec.version = QC::VERSION - spec.description = "queue_classic is a queueing library for Ruby apps. (Rails, Sinatra, Etc...) queue_classic features asynchronous job polling, database maintained locks and no ridiculous dependencies. As a matter of fact, queue_classic only requires pg." - spec.summary = "Simple, efficient worker queue for Ruby & PostgreSQL." - spec.authors = ["Ryan Smith (♠ ace hacker)"] - spec.homepage = "https://github.com/QueueClassic/queue_classic" - spec.license = "MIT" + spec.description = 'queue_classic is a queueing library for Ruby apps. (Rails, Sinatra, Etc...) queue_classic features asynchronous job polling, database maintained locks and no ridiculous dependencies. As a matter of fact, queue_classic only requires pg.' + spec.summary = 'Simple, efficient worker queue for Ruby & PostgreSQL.' + spec.authors = ['Ryan Smith (♠ ace hacker)'] + spec.homepage = 'https://github.com/QueueClassic/queue_classic' + spec.license = 'MIT' spec.files = `git ls-files -z`.split("\x0") spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) } - spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) - spec.require_paths = ["lib"] - spec.require_paths = %w[lib] - spec.add_dependency "pg", ">= 1.1", "< 2.0" - spec.add_development_dependency "activerecord", ">= 5.0.0", "< 6.1" + spec.metadata = { + 'bug_tracker_uri' => 'https://github.com/QueueClassic/queue_classic/issues', + 'changelog_uri' => 'https://github.com/QueueClassic/queue_classic/blob/master/CHANGELOG.md', + 'source_code_uri' => 'https://github.com/QueueClassic/queue_classic', + 'rubygems_mfa_required' => 'true' + } + + spec.required_ruby_version = '>= 3.0.0' + spec.add_runtime_dependency 'pg', '>= 1.1', '< 2.0' end diff --git a/test/benchmark_test.rb b/test/benchmark_test.rb index 8a698e6b..168644d6 100644 --- a/test/benchmark_test.rb +++ b/test/benchmark_test.rb @@ -2,16 +2,16 @@ require_relative 'helper' -if ENV["QC_BENCHMARK"] +if ENV['QC_BENCHMARK'] class BenchmarkTest < QCTest - BENCHMARK_SIZE = Integer(ENV.fetch("QC_BENCHMARK_SIZE", 10_000)) - BENCHMARK_MAX_TIME_DEQUEUE = Integer(ENV.fetch("QC_BENCHMARK_MAX_TIME_DEQUEUE", 30)) - BENCHMARK_MAX_TIME_ENQUEUE = Integer(ENV.fetch("QC_BENCHMARK_MAX_TIME_ENQUEUE", 5)) + BENCHMARK_SIZE = Integer(ENV.fetch('QC_BENCHMARK_SIZE', 10_000)) + BENCHMARK_MAX_TIME_DEQUEUE = Integer(ENV.fetch('QC_BENCHMARK_MAX_TIME_DEQUEUE', 30)) + BENCHMARK_MAX_TIME_ENQUEUE = Integer(ENV.fetch('QC_BENCHMARK_MAX_TIME_ENQUEUE', 5)) def test_enqueue start = Time.now BENCHMARK_SIZE.times do - QC.enqueue("1.odd?") + QC.enqueue('1.odd?') end assert_equal(BENCHMARK_SIZE, QC.count) @@ -23,7 +23,7 @@ def test_dequeue worker = QC::Worker.new worker.running = true BENCHMARK_SIZE.times do - QC.enqueue("1.odd?") + QC.enqueue('1.odd?') end assert_equal(BENCHMARK_SIZE, QC.count) @@ -36,6 +36,5 @@ def test_dequeue assert_equal(0, QC.count) assert_operator(elapsed, :<, BENCHMARK_MAX_TIME_DEQUEUE) end - end end diff --git a/test/config_test.rb b/test/config_test.rb index 4351254f..40babcff 100644 --- a/test/config_test.rb +++ b/test/config_test.rb @@ -12,12 +12,12 @@ def teardown end def test_app_name_default - assert_equal "queue_classic", QC.app_name + assert_equal 'queue_classic', QC.app_name end def test_configure_app_name_with_env_var - with_env "QC_APP_NAME" => "zomg_qc" do - assert_equal "zomg_qc", QC.app_name + with_env 'QC_APP_NAME' => 'zomg_qc' do + assert_equal 'zomg_qc', QC.app_name end end @@ -26,31 +26,31 @@ def test_wait_time_default end def test_configure_wait_time_with_env_var - with_env "QC_LISTEN_TIME" => "7" do + with_env 'QC_LISTEN_TIME' => '7' do assert_equal 7, QC.wait_time end end def test_table_name_default - assert_equal "queue_classic_jobs", QC.table_name + assert_equal 'queue_classic_jobs', QC.table_name end def test_queue_default - assert_equal "default", QC.queue - assert_equal "default", QC.default_queue.name + assert_equal 'default', QC.queue + assert_equal 'default', QC.default_queue.name end def test_configure_queue_with_env_var - with_env "QUEUE" => "priority" do - assert_equal "priority", QC.queue - assert_equal "priority", QC.default_queue.name + with_env 'QUEUE' => 'priority' do + assert_equal 'priority', QC.queue + assert_equal 'priority', QC.default_queue.name end end def test_assign_default_queue - QC.default_queue = QC::Queue.new "dispensable" - assert_equal "default", QC.queue - assert_equal "dispensable", QC.default_queue.name + QC.default_queue = QC::Queue.new 'dispensable' + assert_equal 'default', QC.queue + assert_equal 'dispensable', QC.default_queue.name end def test_queues_default @@ -58,14 +58,14 @@ def test_queues_default end def test_configure_queues_with_env_var - with_env "QUEUES" => "first,second,third" do - assert_equal %w(first second third), QC.queues + with_env 'QUEUES' => 'first,second,third' do + assert_equal %w[first second third], QC.queues end end def test_configure_queues_with_whitespace - with_env "QUEUES" => " one, two, three " do - assert_equal %w(one two three), QC.queues + with_env 'QUEUES' => ' one, two, three ' do + assert_equal %w[one two three], QC.queues end end @@ -74,7 +74,7 @@ def test_top_bound_default end def test_configure_top_bound_with_env_var - with_env "QC_TOP_BOUND" => "5" do + with_env 'QC_TOP_BOUND' => '5' do assert_equal 5, QC.top_bound end end @@ -84,7 +84,7 @@ def test_fork_worker_default end def test_configure_fork_worker_with_env_var - with_env "QC_FORK_WORKER" => "yo" do + with_env 'QC_FORK_WORKER' => 'yo' do assert QC.fork_worker? end end @@ -93,21 +93,32 @@ def test_configuration_constants_are_deprecated warning = capture_stderr_output do QC::FORK_WORKER end - assert_match "QC::FORK_WORKER is deprecated", warning - assert_match "QC.fork_worker? instead", warning + assert_match 'QC::FORK_WORKER is deprecated', warning + assert_match 'QC.fork_worker? instead', warning end class TestWorker < QC::Worker; end def test_default_worker_class + QC.default_worker_class = nil + with_env 'QC_DEFAULT_WORKER_CLASS' => nil do + assert_equal QC::Worker, QC.default_worker_class + end + + QC.default_worker_class = nil assert_equal QC::Worker, QC.default_worker_class end - def test_configure_default_worker_class_with_env_var - if RUBY_VERSION =~ /^1\.9\./ - skip "Kernel.const_get in Ruby 1.9.x does not perform recursive lookups" + def test_invite_worker_class + QC.default_worker_class = nil + with_env 'QC_DEFAULT_WORKER_CLASS' => 'Hopefully::Does::Not::Exist' do + assert_equal QC::Worker, QC.default_worker_class end - with_env "QC_DEFAULT_WORKER_CLASS" => "ConfigTest::TestWorker" do + end + + def test_configure_default_worker_class_with_env_var + QC.default_worker_class = nil + with_env 'QC_DEFAULT_WORKER_CLASS' => 'ConfigTest::TestWorker' do assert_equal TestWorker, QC.default_worker_class end end diff --git a/test/hard_coding_test.rb b/test/hard_coding_test.rb index 3b2e8849..d49d955d 100644 --- a/test/hard_coding_test.rb +++ b/test/hard_coding_test.rb @@ -13,6 +13,8 @@ def test_for_hard_coded_table_names # # # - assert_equal `grep queue_classic_jobs lib -R`.split("\n").sort, ['lib/queue_classic/config.rb: @table_name ||= "queue_classic_jobs"', 'lib/queue_classic/setup.rb: conn.execute("DROP TABLE IF EXISTS queue_classic_jobs CASCADE")'].sort + assert_equal `grep queue_classic_jobs lib -R`.split("\n").sort, + ['lib/queue_classic/config.rb: @table_name ||= \'queue_classic_jobs\'', + 'lib/queue_classic/setup.rb: conn.execute(\'DROP TABLE IF EXISTS queue_classic_jobs CASCADE\')'].sort end end diff --git a/test/helper.rb b/test/helper.rb index 63218f69..d297896b 100644 --- a/test/helper.rb +++ b/test/helper.rb @@ -1,24 +1,23 @@ # frozen_string_literal: true -require "bundler" -require "minitest/reporters" +require 'bundler' +require 'minitest/reporters' Bundler.setup :default, :test -if ENV['CIRCLECI'] == "true" +if ENV['CIRCLECI'] == 'true' Minitest::Reporters.use! Minitest::Reporters::JUnitReporter.new else Minitest::Reporters.use! Minitest::Reporters::SpecReporter.new end -ENV["DATABASE_URL"] ||= "postgres:///queue_classic_test" +ENV['DATABASE_URL'] ||= 'postgres:///queue_classic_test' require_relative '../lib/queue_classic' -require "stringio" -require "minitest/autorun" +require 'stringio' +require 'minitest/autorun' class QCTest < Minitest::Test - def setup init_db end @@ -46,10 +45,10 @@ def capture_stderr_output end def capture_debug_output - original_debug = ENV['DEBUG'] + original_debug = ENV.fetch('DEBUG', nil) original_stdout = $stdout - ENV['DEBUG'] = "true" + ENV['DEBUG'] = 'true' $stdout = StringIO.new yield $stdout.string @@ -61,7 +60,7 @@ def capture_debug_output def with_env(temporary_environment) original_environment = {} temporary_environment.each do |name, value| - original_environment[name] = ENV[name] + original_environment[name] = ENV.fetch(name, nil) ENV[name] = value end yield @@ -84,7 +83,7 @@ def stub_any_instance(class_name, method_name, definition) else message = "#{class_name} does not have method #{method_name}." message << "\nAvailable methods: #{class_name.instance_methods(false)}" - raise ArgumentError.new message + raise ArgumentError, message end ensure if method_present diff --git a/test/lib/queue_classic_rails_connection_test.rb b/test/lib/queue_classic_rails_connection_test.rb index f7270db8..19dcacd5 100644 --- a/test/lib/queue_classic_rails_connection_test.rb +++ b/test/lib/queue_classic_rails_connection_test.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require File.expand_path("../../helper.rb", __FILE__) +require File.expand_path('../helper.rb', __dir__) class QueueClassicRailsConnectionTest < QCTest def before_setup @@ -16,21 +16,22 @@ def before_teardown end def test_uses_active_record_connection_if_exists - connection = get_connection + connection = test_connection QC.default_conn_adapter.execute('SELECT 1;') connection.verify end def test_does_not_use_active_record_connection_if_env_var_set with_env 'QC_RAILS_DATABASE' => 'false' do - connection = get_connection + connection = test_connection QC.default_conn_adapter.execute('SELECT 1;') assert_raises(MockExpectationError) { connection.verify } end end private - def get_connection + + def test_connection connection = Minitest::Mock.new connection.expect(:raw_connection, QC::ConnAdapter.new(active_record_connection_share: true).connection) diff --git a/test/lib/queue_classic_test.rb b/test/lib/queue_classic_test.rb index 60a8470d..d18bc622 100644 --- a/test/lib/queue_classic_test.rb +++ b/test/lib/queue_classic_test.rb @@ -1,13 +1,15 @@ # frozen_string_literal: true -require File.expand_path("../../helper.rb", __FILE__) +require File.expand_path('../helper.rb', __dir__) class QueueClassicTest < QCTest def test_only_delegate_calls_to_queue_it_understands e = assert_raises(NoMethodError) do QC.probably_not end - assert_match "undefined method `probably_not' for QC:Module", e.message + + assert_match(/probably_not/, e.message) + assert_match(/undefined method/, e.message) end def test_default_conn_adapter_default_value diff --git a/test/lib/queue_classic_test_with_activerecord_typecast.rb b/test/lib/queue_classic_test_with_activerecord_typecast.rb index a2c08029..5264ec8b 100644 --- a/test/lib/queue_classic_test_with_activerecord_typecast.rb +++ b/test/lib/queue_classic_test_with_activerecord_typecast.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require File.expand_path("../../helper.rb", __FILE__) +require File.expand_path('../helper.rb', __dir__) class QueueClassicTest < QCTest def before_teardown @@ -12,9 +12,10 @@ def before_teardown def test_lock_with_active_record_timestamp_type_cast # Insert an unlocked job - p_queue = QC::Queue.new("priority_queue") + p_queue = QC::Queue.new('priority_queue') conn_adapter = Minitest::Mock.new - conn_adapter.expect(:execute, {"id" => '1', "q_name" => 'test', "method" => "Kernel.puts", "args" => "[]", "scheduled_at" => Time.now}, [String, String]) + conn_adapter.expect(:execute, + { 'id' => '1', 'q_name' => 'test', 'method' => 'Kernel.puts', 'args' => '[]', 'scheduled_at' => Time.now }, [String, String]) QC.default_conn_adapter = conn_adapter assert_equal(p_queue.lock, {}) end diff --git a/test/queue_test.rb b/test/queue_test.rb index ba1e5617..ad85b8ed 100644 --- a/test/queue_test.rb +++ b/test/queue_test.rb @@ -3,11 +3,10 @@ require_relative 'helper' class QueueTest < QCTest - ResetError = Class.new(PG::Error) def test_enqueue - QC.enqueue("Klass.method") + QC.enqueue('Klass.method') end def test_respond_to @@ -15,13 +14,13 @@ def test_respond_to end def test_lock - queue = QC::Queue.new("queue_classic_jobs") - queue.enqueue("Klass.method") + queue = QC::Queue.new('queue_classic_jobs') + queue.enqueue('Klass.method') job = queue.lock # See helper.rb for more information about the large initial id number. assert_equal((2**34).to_s, job[:id]) - assert_equal("queue_classic_jobs", job[:q_name]) - assert_equal("Klass.method", job[:method]) + assert_equal('queue_classic_jobs', job[:q_name]) + assert_equal('Klass.method', job[:method]) assert_equal([], job[:args]) end @@ -31,46 +30,46 @@ def test_lock_when_empty def test_lock_with_future_job_with_enqueue_in now = Time.now - QC.enqueue_in(2, "Klass.method") + QC.enqueue_in(2, 'Klass.method') assert_nil QC.lock sleep 2 job = QC.lock - assert_equal("Klass.method", job[:method]) + assert_equal('Klass.method', job[:method]) assert_equal([], job[:args]) assert_equal((now + 2).to_i, job[:scheduled_at].to_i) end def test_lock_with_future_job_with_enqueue_at_with_a_time_object future = Time.now + 2 - QC.enqueue_at(future, "Klass.method") + QC.enqueue_at(future, 'Klass.method') assert_nil QC.lock - until Time.now >= future do sleep 0.1 end + sleep 0.1 until Time.now >= future job = QC.lock - assert_equal("Klass.method", job[:method]) + assert_equal('Klass.method', job[:method]) assert_equal([], job[:args]) assert_equal(future.to_i, job[:scheduled_at].to_i) end def test_lock_with_future_job_with_enqueue_at_with_a_float_timestamp offset = (Time.now + 2).to_f - QC.enqueue_at(offset, "Klass.method") + QC.enqueue_at(offset, 'Klass.method') assert_nil QC.lock sleep 2 job = QC.lock - assert_equal("Klass.method", job[:method]) + assert_equal('Klass.method', job[:method]) assert_equal([], job[:args]) end def test_count - QC.enqueue("Klass.method") + QC.enqueue('Klass.method') assert_equal(1, QC.count) - QC.enqueue("Klass.method") + QC.enqueue('Klass.method') assert_equal(2, QC.count) assert_equal(2, QC.count_ready) assert_equal(0, QC.count_scheduled) - QC.enqueue_in(60, "Klass.method") + QC.enqueue_in(60, 'Klass.method') assert_equal(3, QC.count) assert_equal(2, QC.count_ready) assert_equal(1, QC.count_scheduled) @@ -81,25 +80,25 @@ def test_count end def test_delete - QC.enqueue("Klass.method") + QC.enqueue('Klass.method') assert_equal(1, QC.count) QC.delete(QC.lock[:id]) assert_equal(0, QC.count) end def test_delete_all - QC.enqueue("Klass.method") - QC.enqueue("Klass.method") + QC.enqueue('Klass.method') + QC.enqueue('Klass.method') assert_equal(2, QC.count) QC.delete_all assert_equal(0, QC.count) end def test_delete_all_by_queue_name - p_queue = QC::Queue.new("priority_queue") - s_queue = QC::Queue.new("secondary_queue") - p_queue.enqueue("Klass.method") - s_queue.enqueue("Klass.method") + p_queue = QC::Queue.new('priority_queue') + s_queue = QC::Queue.new('secondary_queue') + p_queue.enqueue('Klass.method') + s_queue.enqueue('Klass.method') assert_equal(1, p_queue.count) assert_equal(1, s_queue.count) p_queue.delete_all @@ -108,74 +107,91 @@ def test_delete_all_by_queue_name end def test_queue_instance - queue = QC::Queue.new("queue_classic_jobs") - queue.enqueue("Klass.method") + queue = QC::Queue.new('queue_classic_jobs') + queue.enqueue('Klass.method') assert_equal(1, queue.count) queue.delete(queue.lock[:id]) assert_equal(0, queue.count) end def test_repair_after_error - queue = QC::Queue.new("queue_classic_jobs") + queue = QC::Queue.new('queue_classic_jobs') queue.conn_adapter = QC::ConnAdapter.new - queue.enqueue("Klass.method") + queue.enqueue('Klass.method') assert_equal(1, queue.count) - conn = queue.conn_adapter.connection - def conn.exec(*args); raise(PG::Error); end - def conn.reset(*args); raise(ResetError) end - # We ensure that the reset method is called on the connection. - assert_raises(PG::Error, ResetError) {queue.enqueue("Klass.other_method")} + + queue.conn_adapter.connection.stub :exec, ->(*_args) { raise(PG::Error) } do + queue.conn_adapter.connection.stub :reset, ->(*_args) { raise(ResetError) } do + assert_raises(PG::Error, ResetError) { queue.enqueue('Klass.other_method') } + end + end + queue.conn_adapter.disconnect end def test_enqueue_retry - queue = QC::Queue.new("queue_classic_jobs") + queue = QC::Queue.new('queue_classic_jobs') queue.conn_adapter = QC::ConnAdapter.new conn = queue.conn_adapter.connection - conn.exec('select pg_terminate_backend(pg_backend_pid())') rescue nil - queue.enqueue("Klass.method") + begin + conn.exec('select pg_terminate_backend(pg_backend_pid())') + rescue StandardError + nil + end + queue.enqueue('Klass.method') assert_equal(1, queue.count) queue.conn_adapter.disconnect end def test_enqueue_stops_retrying_on_permanent_error - queue = QC::Queue.new("queue_classic_jobs") + queue = QC::Queue.new('queue_classic_jobs') queue.conn_adapter = QC::ConnAdapter.new - conn = queue.conn_adapter.connection - conn.exec('select pg_terminate_backend(pg_backend_pid())') rescue nil + # Simulate permanent connection error - def conn.exec(*args); raise(PG::Error); end - # Ensure that the error is reraised on second time - assert_raises(PG::Error) {queue.enqueue("Klass.other_method")} + queue.conn_adapter.connection.stub :exec, ->(*_args) { raise(PG::Error) } do + assert_raises(PG::Error) { queue.enqueue('Klass.other_method') } + end + queue.conn_adapter.disconnect end def test_enqueue_in_retry - queue = QC::Queue.new("queue_classic_jobs") + queue = QC::Queue.new('queue_classic_jobs') queue.conn_adapter = QC::ConnAdapter.new conn = queue.conn_adapter.connection - conn.exec('select pg_terminate_backend(pg_backend_pid())') rescue nil - queue.enqueue_in(10,"Klass.method") + begin + conn.exec('select pg_terminate_backend(pg_backend_pid())') + rescue StandardError + nil + end + queue.enqueue_in(10, 'Klass.method') assert_equal(1, queue.count) queue.conn_adapter.disconnect end def test_enqueue_in_stops_retrying_on_permanent_error - queue = QC::Queue.new("queue_classic_jobs") + queue = QC::Queue.new('queue_classic_jobs') queue.conn_adapter = QC::ConnAdapter.new - conn = queue.conn_adapter.connection - conn.exec('select pg_terminate_backend(pg_backend_pid())') rescue nil + + begin + queue.conn_adapter.connection.exec('select pg_terminate_backend(pg_backend_pid())') + rescue StandardError + nil + end + # Simulate permanent connection error - def conn.exec(*args); raise(PG::Error); end - # Ensure that the error is reraised on second time - assert_raises(PG::Error) {queue.enqueue_in(10,"Klass.method")} + queue.conn_adapter.connection.stub :exec, ->(*_args) { raise(PG::Error) } do + # Ensure that the error is reraised on second time + assert_raises(PG::Error) { queue.enqueue_in(10, 'Klass.method') } + end queue.conn_adapter.disconnect end def test_custom_default_queue queue_class = Class.new do attr_accessor :jobs - def enqueue(method, *args) + + def enqueue(method, *_args) @jobs ||= [] @jobs << method end @@ -184,10 +200,10 @@ def enqueue(method, *args) queue_instance = queue_class.new QC.default_queue = queue_instance - QC.enqueue("Klass.method1") - QC.enqueue("Klass.method2") + QC.enqueue('Klass.method1') + QC.enqueue('Klass.method2') - assert_equal ["Klass.method1", "Klass.method2"], queue_instance.jobs + assert_equal ['Klass.method1', 'Klass.method2'], queue_instance.jobs ensure QC.default_queue = nil end @@ -235,34 +251,34 @@ def test_multi_threaded_server_each_thread_acquires_unique_connection def test_enqueue_triggers_notify adapter = QC.default_conn_adapter - adapter.execute('LISTEN "' + QC.queue + '"') + adapter.execute("LISTEN \"#{QC.queue}\"") adapter.send(:drain_notify) msgs = adapter.send(:wait_for_notify, 0.25) assert_equal(0, msgs.length) - QC.enqueue("Klass.method") + QC.enqueue('Klass.method') msgs = adapter.send(:wait_for_notify, 0.25) assert_equal(1, msgs.length) end def test_enqueue_returns_job_id - enqueued_job = QC.enqueue("Klass.method") + enqueued_job = QC.enqueue('Klass.method') locked_job = QC.lock - assert_equal enqueued_job, "id" => locked_job[:id] + assert_equal enqueued_job, 'id' => locked_job[:id] end def test_enqueue_in_returns_job_id - enqueued_job = QC.enqueue_in(1, "Klass.method") + enqueued_job = QC.enqueue_in(1, 'Klass.method') sleep 1 locked_job = QC.lock - assert_equal enqueued_job, "id" => locked_job[:id] + assert_equal enqueued_job, 'id' => locked_job[:id] end def test_enqueue_at_returns_job_id - enqueued_job = QC.enqueue_at(Time.now + 1, "Klass.method") + enqueued_job = QC.enqueue_at(Time.now + 1, 'Klass.method') sleep 1 locked_job = QC.lock - assert_equal enqueued_job, "id" => locked_job[:id] + assert_equal enqueued_job, 'id' => locked_job[:id] end end diff --git a/test/worker_test.rb b/test/worker_test.rb index 8bfe7314..aab91578 100644 --- a/test/worker_test.rb +++ b/test/worker_test.rb @@ -3,11 +3,22 @@ require_relative 'helper' module TestObject - extend self - def no_args; return nil; end - def one_arg(a); return a; end - def two_args(a,b); return [a,b]; end - def forty_two; OpenStruct.new(number: 42); end + module_function + + def no_args = nil + def one_arg(a) = a + def two_args(a, b) = [a, b] + def forty_two = Struct.new(:number).new(42) + + def fail_on_args(a = nil) + raise "fail on args called with #{a}" unless a.nil? + + Class.new do + def number(n) + n * 2 + end + end.new + end end # This not only allows me to test what happens @@ -17,12 +28,12 @@ def forty_two; OpenStruct.new(number: 42); end class TestWorker < QC::Worker attr_accessor :failed_count - def initialize(args={}) - super(args.merge(:connection => QC.default_conn_adapter.connection)) + def initialize(args = {}) + super(args.merge(connection: QC.default_conn_adapter.connection)) @failed_count = 0 end - def handle_failure(job,e) + def handle_failure(job, e) @failed_count += 1 super end @@ -30,7 +41,7 @@ def handle_failure(job,e) class WorkerTest < QCTest def test_work - QC.enqueue("TestObject.no_args") + QC.enqueue('TestObject.no_args') worker = TestWorker.new assert_equal(1, QC.count) worker.work @@ -39,7 +50,7 @@ def test_work end def test_failed_job - QC.enqueue("TestObject.not_a_method") + QC.enqueue('TestObject.not_a_method') worker = TestWorker.new capture_stderr_output { worker.work } assert_equal(1, worker.failed_count) @@ -47,7 +58,7 @@ def test_failed_job def test_failed_job_is_logged output = capture_stderr_output do - QC.enqueue("TestObject.not_a_method") + QC.enqueue('TestObject.not_a_method') TestWorker.new.work end assert(output.include?("# "test") do - 0 == 1 + QC.log_yield(action: 'test') do + 1.zero? end end expected_output = /lib=queue-classic action=test elapsed=\d*/ @@ -65,101 +76,113 @@ def test_log_yield def test_log output = capture_debug_output do - QC.log(:action => "test") + QC.log(action: 'test') end expected_output = /lib=queue-classic action=test/ assert_match(expected_output, output, "=== debug output ===\n #{output}") end def test_work_with_no_args - QC.enqueue("TestObject.no_args") + QC.enqueue('TestObject.no_args') worker = TestWorker.new r = worker.work assert_nil(r) assert_equal(0, worker.failed_count) end + def test_passes_arg_to_last_method + assert_equal(TestObject.fail_on_args.number(2), 4) + assert_raises(RuntimeError) { TestObject.fail_on_args(1).number(2) } + + QC.enqueue('TestObject.fail_on_args.number', 'aa') + worker = TestWorker.new + + r = worker.work + assert_equal(0, worker.failed_count) + assert_equal('aaaa', r) + end + def test_work_with_one_arg - QC.enqueue("TestObject.one_arg", "1") + QC.enqueue('TestObject.one_arg', '1') worker = TestWorker.new r = worker.work - assert_equal("1", r) + assert_equal('1', r) assert_equal(0, worker.failed_count) end def test_work_with_two_args - QC.enqueue("TestObject.two_args", "1", 2) + QC.enqueue('TestObject.two_args', '1', 2) worker = TestWorker.new r = worker.work - assert_equal(["1", 2], r) + assert_equal(['1', 2], r) assert_equal(0, worker.failed_count) end def test_work_custom_queue - p_queue = QC::Queue.new("priority_queue") - p_queue.enqueue("TestObject.two_args", "1", 2) - worker = TestWorker.new(q_name: "priority_queue") + p_queue = QC::Queue.new('priority_queue') + p_queue.enqueue('TestObject.two_args', '1', 2) + worker = TestWorker.new(q_name: 'priority_queue') r = worker.work - assert_equal(["1", 2], r) + assert_equal(['1', 2], r) assert_equal(0, worker.failed_count) end def test_worker_listens_on_chan - p_queue = QC::Queue.new("priority_queue") + p_queue = QC::Queue.new('priority_queue') # Use a new connection because the default connection # will be locked by the sleeping worker. p_queue.conn_adapter = QC::ConnAdapter.new # The wait interval is extreme to demonstrate # that the worker is in fact being activated by a NOTIFY. - worker = TestWorker.new(:q_name => "priority_queue", :wait_interval => 100) + worker = TestWorker.new(q_name: 'priority_queue', wait_interval: 100) t = Thread.new do r = worker.work - assert_equal(["1", 2], r) + assert_equal(['1', 2], r) assert_equal(0, worker.failed_count) end - sleep(0.5) #Give the thread some time to start the worker. - p_queue.enqueue("TestObject.two_args", "1", 2) + sleep(0.5) # Give the thread some time to start the worker. + p_queue.enqueue('TestObject.two_args', '1', 2) p_queue.conn_adapter.disconnect t.join end def test_worker_reuses_conn - QC.enqueue("TestObject.no_args") - count = QC.default_conn_adapter.execute("SELECT count(*) from pg_stat_activity where datname = current_database()")["count"].to_i; + QC.enqueue('TestObject.no_args') + count = QC.default_conn_adapter.execute('SELECT count(*) from pg_stat_activity where datname = current_database()')['count'].to_i worker = TestWorker.new worker.work - new_count = QC.default_conn_adapter.execute("SELECT count(*) from pg_stat_activity where datname = current_database()")["count"].to_i; + new_count = QC.default_conn_adapter.execute('SELECT count(*) from pg_stat_activity where datname = current_database()')['count'].to_i assert( new_count == count, - "Worker should not initialize new connections to #{ QC.default_conn_adapter.send(:db_url) }." + "Worker should not initialize new connections to #{QC.default_conn_adapter.send(:db_url)}." ) end def test_worker_can_work_multiple_queues - p_queue = QC::Queue.new("priority_queue") - p_queue.enqueue("TestObject.two_args", "1", 2) + p_queue = QC::Queue.new('priority_queue') + p_queue.enqueue('TestObject.two_args', '1', 2) - s_queue = QC::Queue.new("secondary_queue") - s_queue.enqueue("TestObject.two_args", "1", 2) + s_queue = QC::Queue.new('secondary_queue') + s_queue.enqueue('TestObject.two_args', '1', 2) - worker = TestWorker.new(:q_names => ["priority_queue", "secondary_queue"]) + worker = TestWorker.new(q_names: %w[priority_queue secondary_queue]) 2.times do r = worker.work - assert_equal(["1", 2], r) + assert_equal(['1', 2], r) assert_equal(0, worker.failed_count) end end def test_worker_works_multiple_queue_left_to_right - l_queue = QC::Queue.new("left_queue") - r_queue = QC::Queue.new("right_queue") + l_queue = QC::Queue.new('left_queue') + r_queue = QC::Queue.new('right_queue') - 3.times { l_queue.enqueue("TestObject.two_args", "1", 2) } - 3.times { r_queue.enqueue("TestObject.two_args", "1", 2) } + 3.times { l_queue.enqueue('TestObject.two_args', '1', 2) } + 3.times { r_queue.enqueue('TestObject.two_args', '1', 2) } - worker = TestWorker.new(:q_names => ["left_queue", "right_queue"]) + worker = TestWorker.new(q_names: %w[left_queue right_queue]) worker.work assert_equal(2, l_queue.count) @@ -171,7 +194,7 @@ def test_worker_works_multiple_queue_left_to_right end def test_work_with_more_complex_construct - QC.enqueue("TestObject.forty_two.number") + QC.enqueue('TestObject.forty_two.number') worker = TestWorker.new r = worker.work assert_equal(42, r) @@ -179,9 +202,9 @@ def test_work_with_more_complex_construct end def test_init_worker_with_database_url - with_database ENV['DATABASE_URL'] || ENV['QC_DATABASE_URL'] do + with_database ENV['DATABASE_URL'] || ENV.fetch('QC_DATABASE_URL', nil) do worker = QC::Worker.new - QC.enqueue("TestObject.no_args") + QC.enqueue('TestObject.no_args') worker.lock_job QC.default_conn_adapter.disconnect @@ -192,133 +215,111 @@ def test_init_worker_without_conn with_database nil do assert_raises(ArgumentError) do worker = QC::Worker.new - QC.enqueue("TestObject.no_args") + QC.enqueue('TestObject.no_args') worker.lock_job end end end def test_worker_unlocks_job_on_signal_exception - job_details = QC.enqueue("Kernel.eval", "raise SignalException.new('INT')") + job_details = QC.enqueue('Kernel.eval', "raise SignalException.new('INT')") worker = TestWorker.new unlocked = nil - fake_unlock = Proc.new do |job_id| - if job_id == job_details['id'] - unlocked = true - end + fake_unlock = proc do |job_id| + unlocked = true if job_id == job_details['id'] original_unlock(job_id) end stub_any_instance(QC::Queue, :unlock, fake_unlock) do - begin - worker.work - rescue SignalException - ensure - assert unlocked, "SignalException failed to unlock the job in the queue." - end + assert_raises(SignalException) { worker.work } + ensure + assert unlocked, 'SignalException failed to unlock the job in the queue.' end end def test_worker_unlocks_job_on_system_exit - job_details = QC.enqueue("Kernel.eval", "raise SystemExit.new") + job_details = QC.enqueue('Kernel.eval', 'raise SystemExit.new') worker = TestWorker.new unlocked = nil - fake_unlock = Proc.new do |job_id| - if job_id == job_details['id'] - unlocked = true - end + fake_unlock = proc do |job_id| + unlocked = true if job_id == job_details['id'] original_unlock(job_id) end stub_any_instance(QC::Queue, :unlock, fake_unlock) do - begin - worker.work - rescue SystemExit - ensure - assert unlocked, "SystemExit failed to unlock the job in the queue." - end + assert_raises(SystemExit) { worker.work } + ensure + assert unlocked, 'SystemExit failed to unlock the job in the queue.' end end def test_worker_does_not_unlock_jobs_on_syntax_error - job_details = QC.enqueue("Kernel.eval", "bad syntax") + job_details = QC.enqueue('Kernel.eval', 'bad syntax') worker = TestWorker.new unlocked = nil - fake_unlock = Proc.new do |job_id| - if job_id == job_details['id'] - unlocked = true - end + fake_unlock = proc do |job_id| + unlocked = true if job_id == job_details['id'] original_unlock(job_id) end stub_any_instance(QC::Queue, :unlock, fake_unlock) do - begin - errors = capture_stderr_output do - worker.work - end - ensure - message = ["SyntaxError unexpectedly unlocked the job in the queue."] - message << "Errors:\n#{errors}" unless errors.empty? - refute unlocked, message.join("\n") + errors = capture_stderr_output do + worker.work end + ensure + message = ['SyntaxError unexpectedly unlocked the job in the queue.'] + message << "Errors:\n#{errors}" unless errors.empty? + refute unlocked, message.join("\n") end end def test_worker_does_not_unlock_jobs_on_load_error - job_details = QC.enqueue("Kernel.eval", "require 'not_a_real_file'") + job_details = QC.enqueue('Kernel.eval', "require 'not_a_real_file'") worker = TestWorker.new unlocked = nil - fake_unlock = Proc.new do |job_id| - if job_id == job_details['id'] - unlocked = true - end + fake_unlock = proc do |job_id| + unlocked = true if job_id == job_details['id'] original_unlock(job_id) end stub_any_instance(QC::Queue, :unlock, fake_unlock) do - begin - errors = capture_stderr_output do - worker.work - end - ensure - message = ["LoadError unexpectedly unlocked the job in the queue."] - message << "Errors:\n#{errors}" unless errors.empty? - refute unlocked, message.join("\n") + errors = capture_stderr_output do + worker.work end + ensure + message = ['LoadError unexpectedly unlocked the job in the queue.'] + message << "Errors:\n#{errors}" unless errors.empty? + refute unlocked, message.join("\n") end end def test_worker_does_not_unlock_jobs_on_no_memory_error - job_details = QC.enqueue("Kernel.eval", "raise NoMemoryError.new") + job_details = QC.enqueue('Kernel.eval', 'raise NoMemoryError.new') worker = TestWorker.new unlocked = nil - fake_unlock = Proc.new do |job_id| - if job_id == job_details['id'] - unlocked = true - end + fake_unlock = proc do |job_id| + unlocked = true if job_id == job_details['id'] original_unlock(job_id) end stub_any_instance(QC::Queue, :unlock, fake_unlock) do - begin - errors = capture_stderr_output do - worker.work - end - ensure - message = ["NoMemoryError unexpectedly unlocked the job in the queue."] - message << "Errors:\n#{errors}" unless errors.empty? - refute unlocked, message.join("\n") + errors = capture_stderr_output do + worker.work end + ensure + message = ['NoMemoryError unexpectedly unlocked the job in the queue.'] + message << "Errors:\n#{errors}" unless errors.empty? + refute unlocked, message.join("\n") end end @@ -326,8 +327,8 @@ def test_worker_does_not_unlock_jobs_on_no_memory_error def with_database(url) original_conn_adapter = QC.default_conn_adapter - original_database_url = ENV['DATABASE_URL'] - original_qc_database_url = ENV['QC_DATABASE_URL'] + original_database_url = ENV.fetch('DATABASE_URL', nil) + original_qc_database_url = ENV.fetch('QC_DATABASE_URL', nil) ENV['DATABASE_URL'] = ENV['QC_DATABASE_URL'] = url QC.default_conn_adapter = nil