shinq 0.8.0 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 01d64906303f1f93f1c58963b07d97b8d5f63ac7
4
- data.tar.gz: 478a6b80464b1b399c19da69a8e37f4dc8dd7fbd
2
+ SHA256:
3
+ metadata.gz: c0cdad6cb88d95c3f54ef38ac64b7db0314850912693d9f87eae2d88c59c11be
4
+ data.tar.gz: aed02806856d77fda982ae5d2039be2bf4558f32e7732a6d3681299fc25aac26
5
5
  SHA512:
6
- metadata.gz: b165ea04629a8dd68d9e2ea480a27cdd663d69530562721cc726e8ce360a86d61188b031dde01c7da7fbd35d8a639cd2d63441f39e947563043206ff3c82a077
7
- data.tar.gz: 7d8bef34453211d6e7e9bf6abc1ba19c881b7a42552dc76146d65d6e96bff947d090eeb68929e58c229a2998126fbb670a95adef3416f6e304f3aee5c5f20f5e
6
+ metadata.gz: 7f8e92ee4d147158524f66aa2d1bfb596abe66c0dddf49924d1e642a379f8ae30075127cee711dc535da8c97305025a983753802ead7b5f11eebccda399522f7
7
+ data.tar.gz: 45ee3dcf8f20c696c078cb2db67475da5aa66160f1699b0db03108540a42cf641595f05ebd529e5110a9972e61710b7e9644e9881d74e2ffd799618a5cef3207
@@ -3,14 +3,14 @@ sudo: false
3
3
  cache: bundler
4
4
  rvm:
5
5
  - 2.1
6
- - 2.2.0
7
- - 2.2.1
8
- - 2.2.2
9
- - 2.2.7
10
- - 2.3.4
11
- - 2.4.1
6
+ - 2.2
7
+ - 2.3
8
+ - 2.4
9
+ - 2.6
10
+ - 2.7
12
11
  gemfile:
13
12
  - Gemfile
14
13
  before_install:
14
+ - gem update bundler
15
15
  - mysql -e "create database IF NOT EXISTS shinq_test;" -uroot
16
16
  script: bundle exec rspec
data/README.md CHANGED
@@ -58,6 +58,7 @@ class CreateWorkerNames < ActiveRecord::Migration
58
58
  create_table :worker_names, id: false, options: "ENGINE=QUEUE" do |t|
59
59
  t.string :job_id, null: false
60
60
  t.string :title
61
+ t.integer :scheduled_at, limit: 8, default: 0, null: false
61
62
  t.datetime :enqueued_at, null: false
62
63
  end
63
64
  end
@@ -78,6 +79,7 @@ mysql> show create table worker_names\G
78
79
  Create Table: CREATE TABLE `worker_names` (
79
80
  `job_id` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
80
81
  `title` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
82
+ `scheduled_at` bigint(20) NOT NULL DEFAULT '0',
81
83
  `enqueued_at` datetime NOT NULL
82
84
  ) ENGINE=QUEUE DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci
83
85
  ```
@@ -5,6 +5,7 @@ class <%= migration_class_name %> < ActiveRecord::Migration
5
5
  <% attributes.each do |attribute| -%>
6
6
  t.<%= attribute.type %> :<%= attribute.name %><%= attribute.inject_options %>
7
7
  <% end -%>
8
+ t.integer :scheduled_at, limit: 8, default: 0, null: false
8
9
  t.datetime :enqueued_at, null: false
9
10
  end
10
11
  end
@@ -24,12 +24,20 @@ module Shinq
24
24
 
25
25
  def self.setup_db_connection(db_name)
26
26
  raise Shinq::ConfigurationError unless self.configuration.db_defined?(db_name)
27
- @connections[db_name] = Mysql2::Client.new(self.configuration.db_config[db_name])
27
+ @connections[db_name.to_sym] = Mysql2::Client.new(self.configuration.db_config[db_name])
28
28
  end
29
29
 
30
30
  def self.connection(db_name: self.default_db)
31
31
  @connections ||= {}
32
- @connections[db_name] ||= setup_db_connection(db_name)
32
+ @connections[db_name.to_sym] ||= setup_db_connection(db_name)
33
+ end
34
+
35
+ def self.clear_all_connections!
36
+ return unless @connections
37
+ @connections.each do |_db_name, connection|
38
+ connection && connection.close
39
+ end
40
+ @connections = {}
33
41
  end
34
42
 
35
43
  def self.logger
@@ -1,6 +1,14 @@
1
1
  module ActiveJob
2
2
  module QueueAdapters
3
3
  class ShinqAdapter
4
+ def enqueue(job)
5
+ self.class.enqueue job
6
+ end
7
+
8
+ def enqueue_at(job, timestamp)
9
+ self.class.enqueue_at job, timestamp
10
+ end
11
+
4
12
  class << self
5
13
  def enqueue(job)
6
14
  Shinq::Client.enqueue(
@@ -9,6 +17,15 @@ module ActiveJob
9
17
  args: job.arguments.first
10
18
  )
11
19
  end
20
+
21
+ def enqueue_at(job, timestamp)
22
+ Shinq::Client.enqueue(
23
+ table_name: job.queue_name,
24
+ job_id: job.job_id,
25
+ args: job.arguments.first,
26
+ scheduled_at: timestamp,
27
+ )
28
+ end
12
29
  end
13
30
  end
14
31
  end
@@ -15,6 +15,7 @@ module Shinq
15
15
  def initialize(args=ARGV)
16
16
  setup_option(args)
17
17
  bootstrap
18
+ initialize_shinq
18
19
  end
19
20
 
20
21
  def setup_option(args)
@@ -31,10 +32,7 @@ module Shinq
31
32
  end
32
33
 
33
34
  opt.on('-w', '--worker VALUE', 'Name of worker class') do |v|
34
- worker_class = v.camelize.safe_constantize
35
- raise OptionParseError, "worker class #{v.camelize} corresponding to #{v} does not exist" unless worker_class
36
35
  opts[:worker_name] = v
37
- opts[:worker_class] = worker_class
38
36
  end
39
37
 
40
38
  opt.on('-p', '--process VALUE', 'Number of workers') do |v|
@@ -75,6 +73,10 @@ module Shinq
75
73
  opts[:abort_on_error] = v
76
74
  end
77
75
 
76
+ opt.on('--sleep-sec-on-error N', Integer, 'Allow worker to sleep(sec) after exception') do |v|
77
+ opts[:sleep_sec_on_error] = v
78
+ end
79
+
78
80
  opt.on('-v', '--version', 'Print version') do |v|
79
81
  puts "Shinq #{Shinq::VERSION}"
80
82
  exit(0)
@@ -104,6 +106,11 @@ module Shinq
104
106
  end
105
107
  end
106
108
 
109
+ def initialize_shinq
110
+ Shinq::Client.fetch_column_names(table_name: Shinq.configuration.worker_name.pluralize)
111
+ Shinq.configuration.worker_class # check if worker_class is constantizable before running ServerEngine
112
+ end
113
+
107
114
  def run
108
115
  klass = !options.statistics.nil? && options.statistics ? Shinq::Statistics : Shinq::Launcher
109
116
 
@@ -112,8 +119,8 @@ module Shinq
112
119
  worker_type: 'process',
113
120
  pid_file: 'shinq.pid',
114
121
  workers: options.process,
115
- worker_graceful_kill_timeout: options.graceful_kill_timeout
116
- logger: options.daemonize ? Shinq.logger : nil
122
+ worker_graceful_kill_timeout: options.graceful_kill_timeout,
123
+ logger: options.daemonize ? Shinq.logger : nil,
117
124
  })
118
125
 
119
126
  se.run
@@ -8,13 +8,19 @@ module Shinq
8
8
  @builder ||= SQL::Maker.new(driver: 'mysql', auto_bind: true)
9
9
  end
10
10
 
11
- def self.enqueue(table_name: , job_id: , args:)
11
+ def self.enqueue(table_name: , job_id: , args:, scheduled_at: nil)
12
+ if scheduled_at && !schedulable?(table_name: table_name)
13
+ raise ArgumentError, "table #{table_name} is not schedulable. You need column `scheduled_at`"
14
+ end
15
+
12
16
  case args
13
17
  when Hash
14
- sql = builder.insert(table_name, args.merge(
18
+ attributes = args.merge(
15
19
  job_id: job_id,
16
- enqueued_at: Time.now
17
- ))
20
+ scheduled_at: scheduled_at ? scheduled_at.to_i : nil,
21
+ enqueued_at: Time.now,
22
+ ).compact
23
+ sql = builder.insert(table_name, attributes)
18
24
  Shinq.connection.query(sql)
19
25
  else
20
26
  raise ArgumentError, "`args` should be a Hash"
@@ -22,7 +28,9 @@ module Shinq
22
28
  end
23
29
 
24
30
  def self.dequeue(table_name:)
25
- quoted = SQL::Maker::Quoting.quote(table_name)
31
+ condition = schedulable?(table_name: table_name) ? ":scheduled_at<=#{Time.now.to_i}" : ''
32
+ quoted = SQL::Maker::Quoting.quote("#{table_name}#{condition}")
33
+
26
34
  queue_timeout_quoted = SQL::Maker::Quoting.quote(Shinq.configuration.queue_timeout)
27
35
 
28
36
  wait_query = "queue_wait(#{quoted}, #{queue_timeout_quoted})"
@@ -52,6 +60,26 @@ module Shinq
52
60
  )
53
61
  end
54
62
 
63
+ def self.schedulable?(table_name:)
64
+ self.column_names(table_name: table_name).include?('scheduled_at')
65
+ end
66
+
67
+ def self.column_names(table_name:)
68
+ @column_names_by_table_name ||= {}
69
+ @column_names_by_table_name[table_name.to_sym] ||= begin
70
+ quoted = SQL::Maker::Quoting.quote(table_name)
71
+ column = Shinq.connection.query(<<-EOS).map { |record| record['column_name'] }
72
+ select column_name from information_schema.columns where table_schema = database() and table_name = #{quoted}
73
+ EOS
74
+ end
75
+ end
76
+
77
+ def self.fetch_column_names(table_name:)
78
+ @column_names_by_table_name ||= {}
79
+ @column_names_by_table_name.delete(table_name.to_sym)
80
+ column_names(table_name: table_name)
81
+ end
82
+
55
83
  def self.done
56
84
  Shinq.connection.query('select queue_end()')
57
85
  end
@@ -10,7 +10,7 @@ module Shinq
10
10
  # You may need to set it +false+ for jobs which take very long time to proceed.
11
11
  # You may also need to handle performing error manually then.
12
12
  class Configuration
13
- attr_accessor :require, :worker_name, :worker_class, :db_config, :queue_db, :default_db, :process, :graceful_kill_timeout, :queue_timeout, :daemonize, :statistics, :lifecycle, :abort_on_error
13
+ attr_accessor :require, :worker_name, :db_config, :queue_db, :default_db, :process, :graceful_kill_timeout, :queue_timeout, :daemonize, :statistics, :lifecycle, :abort_on_error, :sleep_sec_on_error
14
14
 
15
15
  DEFAULT = {
16
16
  require: '.',
@@ -18,15 +18,25 @@ module Shinq
18
18
  graceful_kill_timeout: 600,
19
19
  queue_timeout: 1,
20
20
  daemonize: false,
21
- abort_on_error: true
21
+ abort_on_error: true,
22
+ sleep_sec_on_error: 1,
22
23
  }
23
24
 
24
25
  def initialize(opts)
25
- %i(require worker_name db_config queue_db default_db process queue_timeout daemonize statistics lifecycle abort_on_error).each do |k|
26
- send(:"#{k}=", opts[k] || DEFAULT[k])
26
+ %i(require worker_name db_config queue_db default_db process queue_timeout daemonize statistics lifecycle abort_on_error sleep_sec_on_error).each do |k|
27
+ value = opts.key?(k) ? opts[k] : DEFAULT[k]
28
+ send(:"#{k}=", value)
27
29
  end
28
30
  end
29
31
 
32
+ def worker_class
33
+ worker_class = worker_name.camelize.safe_constantize
34
+ unless worker_class
35
+ raise ConfigurationError, "worker class #{worker_name.camelize} corresponding to #{worker_name} does not exist"
36
+ end
37
+ worker_class
38
+ end
39
+
30
40
  def default_db_config
31
41
  raise ConfigurationError if !(default_db && db_defined?(default_db))
32
42
  db_config[default_db]
@@ -3,6 +3,10 @@ require 'active_support/inflector'
3
3
 
4
4
  module Shinq
5
5
  module Launcher
6
+ def initialize
7
+ Shinq.clear_all_connections!
8
+ end
9
+
6
10
  # Wait configured queue and proceed each of them until stop.
7
11
  # @see Shinq::Configuration#abort_on_error
8
12
  def run
@@ -12,21 +16,26 @@ module Shinq
12
16
  @loop_count = 0
13
17
 
14
18
  until @stop
15
- queue = Shinq::Client.dequeue(table_name: worker_name.pluralize)
16
- next Shinq.logger.info("Queue is empty (#{Time.now})") unless queue
19
+ begin
20
+ queue = Shinq::Client.dequeue(table_name: worker_name.pluralize)
21
+ next Shinq.logger.info("Queue is empty (#{Time.now})") unless queue
17
22
 
18
- if Shinq.configuration.abort_on_error
19
- begin
23
+ if Shinq.configuration.abort_on_error
24
+ worker_class.new.perform(queue)
25
+ Shinq::Client.done
26
+ else
27
+ Shinq::Client.done
20
28
  worker_class.new.perform(queue)
21
- rescue => e
22
- Shinq::Client.abort
23
- raise e
24
29
  end
25
30
 
26
- Shinq::Client.done
27
- else
28
- Shinq::Client.done
29
- worker_class.new.perform(queue)
31
+ Shinq.clear_all_connections!
32
+ rescue => e
33
+ Shinq.logger.error(format_error_message(e))
34
+ sleep Shinq.configuration.sleep_sec_on_error
35
+
36
+ Shinq::Client.abort if Shinq.configuration.abort_on_error && queue
37
+ Shinq.clear_all_connections!
38
+ break
30
39
  end
31
40
 
32
41
  @loop_count += 1
@@ -46,5 +55,16 @@ module Shinq
46
55
  return false unless Shinq.configuration.lifecycle
47
56
  return (Shinq.configuration.lifecycle < @loop_count)
48
57
  end
58
+
59
+ private
60
+
61
+ def format_error_message(error)
62
+ if defined?(::Rails) && ::Rails.backtrace_cleaner
63
+ backtrace = ::Rails.backtrace_cleaner.clean(error.backtrace || []).presence || error.backtrace
64
+ else
65
+ backtrace = error.backtrace
66
+ end
67
+ "#{error.message} at #{backtrace.join(' <<< ')}"
68
+ end
49
69
  end
50
70
  end
@@ -4,7 +4,7 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
4
 
5
5
  Gem::Specification.new do |spec|
6
6
  spec.name = "shinq"
7
- spec.version = '0.8.0'
7
+ spec.version = '1.0.2'
8
8
  spec.authors = ["Ryoichi SEKIGUCHI"]
9
9
  spec.email = ["ryopeko@gmail.com"]
10
10
  spec.summary = %q{Worker and enqueuer for Q4M using the interface of ActiveJob.}
@@ -16,17 +16,18 @@ Gem::Specification.new do |spec|
16
16
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
17
17
  spec.require_paths = ["lib"]
18
18
 
19
- spec.add_development_dependency "bundler", "~> 1.7"
19
+ spec.add_development_dependency "bundler"
20
20
  spec.add_development_dependency "rake", "~> 10.0"
21
21
  spec.add_development_dependency "pry"
22
22
  spec.add_development_dependency "tapp"
23
23
  spec.add_development_dependency "rspec"
24
24
  spec.add_development_dependency "rspec-mocks"
25
25
  spec.add_development_dependency "simplecov"
26
+ spec.add_development_dependency "timecop"
26
27
 
27
- spec.add_dependency "mysql2", ">= 0.3.16", "< 0.5"
28
+ spec.add_dependency "mysql2", ">= 0.3.16", "< 0.6"
28
29
  spec.add_dependency "sql-maker", "~> 0.0.4"
29
- spec.add_dependency "activesupport", "~> 4.2.0"
30
- spec.add_dependency "activejob", "~> 4.2.0"
30
+ spec.add_dependency "activesupport", ">= 4.2.0", "< 6"
31
+ spec.add_dependency "activejob", ">= 4.2.0", "< 6"
31
32
  spec.add_dependency 'serverengine', '~> 1.5.9'
32
33
  end
@@ -0,0 +1,15 @@
1
+ DROP TABLE IF EXISTS `queue_test`;
2
+ CREATE TABLE `queue_test` (
3
+ `job_id` varchar(255) NOT NULL,
4
+ `title` varchar(255),
5
+ `scheduled_at` bigint(20) NOT NULL DEFAULT '0',
6
+ `enqueued_at` datetime NOT NULL
7
+ ) ENGINE=<%= engine %>;
8
+
9
+ DROP TABLE IF EXISTS `queue_test_without_scheduled_at`;
10
+
11
+ CREATE TABLE `queue_test_without_scheduled_at` (
12
+ `job_id` varchar(255) NOT NULL,
13
+ `title` varchar(255),
14
+ `enqueued_at` datetime NOT NULL
15
+ ) ENGINE=<%= engine %>;
@@ -0,0 +1,4 @@
1
+ require 'active_job'
2
+ class MyWorker < ActiveJob::Base
3
+ queue_as :my_workers
4
+ end
@@ -3,7 +3,6 @@ require 'shinq'
3
3
  require 'shinq/client'
4
4
 
5
5
  describe "Integration", skip: ENV['TRAVIS'] do
6
- let(:queue_table) { 'queue_test' }
7
6
 
8
7
  before do
9
8
  Shinq.configuration = {
@@ -13,28 +12,97 @@ describe "Integration", skip: ENV['TRAVIS'] do
13
12
  end
14
13
 
15
14
  after do
16
- Shinq.connection.query("delete from #{queue_table}")
15
+ Shinq.clear_all_connections! # Return from owner mode
16
+ tables = Shinq.connection.query('show tables').flat_map(&:values)
17
+ tables.each do |table|
18
+ Shinq.connection.query("delete from #{table}")
19
+ end
20
+ Shinq.clear_all_connections!
17
21
  end
18
22
 
19
23
  describe "Shinq::Client.enqueue,dequeue" do
20
- context "valid args" do
21
- let(:args) { {title: 'foo'} }
24
+ before do
25
+ Timecop.freeze
26
+ end
27
+ after do
28
+ Timecop.return
29
+ end
30
+
31
+ context 'with scheduled_at on table' do
32
+ let(:queue_table) { 'queue_test' }
33
+ let(:job_id) { SecureRandom.uuid }
22
34
 
23
35
  before do
24
36
  Shinq::Client.enqueue(
25
37
  table_name: queue_table,
26
- job_id: 'jobid',
27
- args: args
38
+ job_id: job_id,
39
+ args: { title: :foo },
40
+ scheduled_at: scheduled_at
28
41
  )
42
+ end
29
43
 
30
- @queue = Shinq::Client.dequeue(table_name: queue_table)
31
- Shinq::Client.done
44
+ context 'when scheduled_at is not specified' do
45
+ let(:scheduled_at) { nil }
46
+
47
+ it 'can dequeue immediately' do
48
+ expect(Shinq::Client.dequeue(table_name: queue_table)[:job_id]).to eq job_id
49
+ end
32
50
  end
33
51
 
34
- it { expect(@queue[:title]).to eq args[:title] }
52
+ context 'when scheduled_at is specified' do
53
+ let(:scheduled_at) { 1.minute.since }
54
+
55
+ it 'can not dequeue job immediately' do
56
+ expect(Shinq::Client.dequeue(table_name: queue_table)).to be nil
57
+ end
58
+
59
+ context 'when specified time elapsed' do
60
+ before do
61
+ Timecop.travel(scheduled_at)
62
+ end
63
+
64
+ it 'can dequeue job' do
65
+ expect(Shinq::Client.dequeue(table_name: queue_table)[:job_id]).to eq job_id
66
+ end
67
+ end
68
+ end
35
69
  end
36
70
 
37
- context "invalid args" do
71
+ context 'without scheduled_at on table' do
72
+ let(:job_id) { SecureRandom.uuid }
73
+ let(:unschedulable_queue_table) { 'queue_test_without_scheduled_at' }
74
+
75
+ context 'when scheduled_at is not specified' do
76
+ before do
77
+ Shinq::Client.enqueue(
78
+ table_name: unschedulable_queue_table,
79
+ job_id: job_id,
80
+ args: { title: :foo },
81
+ )
82
+ end
83
+
84
+ it 'can dequeue job' do
85
+ expect(Shinq::Client.dequeue(table_name: unschedulable_queue_table)[:job_id]).to eq job_id
86
+ end
87
+ end
88
+
89
+ context 'when scheduled_at is specified' do
90
+ it 'cannot enqueue job' do
91
+ expect {
92
+ Shinq::Client.enqueue(
93
+ table_name: unschedulable_queue_table,
94
+ job_id: job_id,
95
+ args: { title: :foo },
96
+ scheduled_at: 1.minute.since,
97
+ )
98
+ }.to raise_error ArgumentError
99
+ end
100
+ end
101
+ end
102
+
103
+
104
+ context "with invalid args" do
105
+ let(:queue_table) { 'queue_test' }
38
106
  it {
39
107
  expect {
40
108
  Shinq::Client.enqueue(
@@ -48,6 +116,8 @@ describe "Integration", skip: ENV['TRAVIS'] do
48
116
  end
49
117
 
50
118
  describe "Shinq::Client.abort" do
119
+ let(:queue_table) { 'queue_test' }
120
+
51
121
  context "When client has a queue" do
52
122
  before do
53
123
  Shinq::Client.enqueue(
@@ -77,6 +147,8 @@ describe "Integration", skip: ENV['TRAVIS'] do
77
147
  end
78
148
 
79
149
  describe "Shinq::Client.queue_stats" do
150
+ let(:queue_table) { 'queue_test' }
151
+
80
152
  subject(:stats) {
81
153
  Shinq::Client.queue_stats(table_name: queue_table)
82
154
  }
@@ -0,0 +1,38 @@
1
+ require 'spec_helper'
2
+ require 'shinq/cli'
3
+
4
+ describe Shinq::CLI do
5
+ after do
6
+ Shinq.clear_all_connections!
7
+ end
8
+
9
+ describe '.new' do
10
+ context 'when there are require statement' do
11
+ it 'requires and run the code' do
12
+ # NOTE: As CLI alters global process irreversibly, we only check the result
13
+ Shinq::CLI.new(%W[
14
+ --require my_worker
15
+ --db-config #{File.expand_path('../config/database.yml', __dir__)}
16
+ --worker my_worker
17
+ ])
18
+ expect(defined? MyWorker).to eq 'constant'
19
+ end
20
+ end
21
+ end
22
+
23
+ describe '#run' do
24
+ before do
25
+ allow_any_instance_of(ServerEngine::Daemon).to receive(:run).and_return(nil)
26
+ end
27
+
28
+ it 'launches Shinq::Launcher backended by ServerEngine' do
29
+ expect {
30
+ Shinq::CLI.new(%W[
31
+ --require my_worker
32
+ --db-config #{File.expand_path('../config/database.yml', __dir__)}
33
+ --worker my_worker
34
+ ]).run
35
+ }.not_to raise_error
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,46 @@
1
+ require 'spec_helper'
2
+ require 'shinq'
3
+ require 'shinq/client'
4
+
5
+ describe Shinq::Client do
6
+ subject(:shinq_client) do
7
+ Shinq::Client.dup.tap do |client|
8
+ client.instance_variables.each do |variable|
9
+ client.remove_instance_variable(variable)
10
+ end
11
+ end
12
+ end
13
+
14
+ before do
15
+ Shinq.configuration = {
16
+ default_db: :test,
17
+ db_config: load_database_config
18
+ }
19
+ end
20
+
21
+ after do
22
+ Shinq.clear_all_connections!
23
+ end
24
+
25
+ describe '.schedulable?' do
26
+ context 'when target table have scheduled_at' do
27
+ it { expect(shinq_client.schedulable?(table_name: :queue_test)).to be true }
28
+ end
29
+
30
+ context 'when target table does NOT have scheduled_at' do
31
+ it { expect(shinq_client.schedulable?(table_name: :queue_test_without_scheduled_at)).to be false }
32
+ end
33
+ end
34
+
35
+ describe '.column_names' do
36
+ it 'fetches column_names' do
37
+ expect(shinq_client.column_names(table_name: :queue_test)).to eq(['job_id', 'title', 'scheduled_at', 'enqueued_at'])
38
+ end
39
+ end
40
+
41
+ describe 'fetch_column_names' do
42
+ it 'fetches column_names' do
43
+ expect(shinq_client.fetch_column_names(table_name: :queue_test)).to eq(['job_id', 'title', 'scheduled_at', 'enqueued_at'])
44
+ end
45
+ end
46
+ end
@@ -33,6 +33,21 @@ describe Shinq::Configuration do
33
33
  end
34
34
  end
35
35
 
36
+ describe '#worker_class' do
37
+ context 'when worker_name is valid' do
38
+ let(:configuration) { Shinq::Configuration.new(worker_name: 'shinq') }
39
+ it 'constantizes worker_name to corresponding constant' do
40
+ expect(configuration.worker_class).to eq Shinq
41
+ end
42
+ end
43
+
44
+ context 'when worker_name is invalid' do
45
+ let(:configuration) { Shinq::Configuration.new(worker_name: 'invalid_shinq') }
46
+
47
+ it {expect { configuration.worker_class }.to raise_error(Shinq::ConfigurationError)}
48
+ end
49
+ end
50
+
36
51
  describe "#default_db_config" do
37
52
  context "when default_db is present" do
38
53
  let(:configuration) { Shinq::Configuration.new({}) }
@@ -3,29 +3,32 @@ require 'shinq'
3
3
  require 'shinq/configuration'
4
4
  require 'logger'
5
5
 
6
- def shinq_class
7
- Shinq.dup
8
- end
9
-
10
6
  describe Shinq do
11
- subject { shinq_class }
7
+ # remove instance variable deliberately or indeliberately defined by other specs
8
+ subject(:shinq) do
9
+ Shinq.dup.tap do |shinq|
10
+ shinq.instance_variables.each do |variable|
11
+ shinq.remove_instance_variable(variable)
12
+ end
13
+ end
14
+ end
15
+
16
+ after do
17
+ shinq.clear_all_connections!
18
+ end
12
19
 
13
20
  it { is_expected.to respond_to(:configuration) }
14
21
  it { is_expected.to respond_to(:configuration=) }
15
22
 
16
23
  describe ".configuration" do
17
24
  context "when configuration is not present" do
18
- let(:shinq) { shinq_class }
19
-
20
25
  it { expect(shinq.configuration).to be_a_kind_of(Shinq::Configuration) }
21
26
  end
22
27
 
23
28
  context "when configuration is present" do
24
- let(:shinq) {
25
- shinq_class.tap {|s|
26
- s.configuration = Hash.new
27
- }
28
- }
29
+ before do
30
+ shinq.configuration = Hash.new
31
+ end
29
32
 
30
33
  it { expect(shinq.configuration).to be_a_kind_of(Shinq::Configuration) }
31
34
  end
@@ -33,7 +36,6 @@ describe Shinq do
33
36
 
34
37
  describe ".configuration=" do
35
38
  context "when specify args is Hash" do
36
- let(:shinq) { shinq_class }
37
39
  let(:args) { Hash.new }
38
40
 
39
41
  it 'is expect to return specified args' do
@@ -42,7 +44,6 @@ describe Shinq do
42
44
  end
43
45
 
44
46
  context "when specify args is Shinq::Configuration" do
45
- let(:shinq) { shinq_class }
46
47
  let(:args) { Shinq::Configuration.new({}) }
47
48
 
48
49
  it 'is expect to return specified args' do
@@ -52,39 +53,56 @@ describe Shinq do
52
53
  end
53
54
 
54
55
  describe ".connection" do
56
+ context "when db_config is not present" do
57
+ it { expect{ shinq.connection(db_name: :unknown) }.to raise_error(Shinq::ConfigurationError) }
58
+ end
59
+
55
60
  context "when db_config is present" do
56
- let(:shinq) { shinq_class }
61
+ before do
62
+ shinq.configuration = { db_config: load_database_config, default_db: :test }
63
+ end
57
64
 
58
- it { expect{ shinq.connection(db_name: :unknown) }.to raise_error(Shinq::ConfigurationError) }
65
+ it do
66
+ shinq.connection(db_name: :test)
67
+ expect(shinq.connection(db_name: :test)).to be_a_kind_of(Mysql2::Client)
68
+ end
59
69
  end
70
+ end
60
71
 
61
- context "when db_config is not preset" do
62
- let(:shinq) {
63
- shinq_class.tap {|s|
64
- s.configuration = {
65
- db_config: load_database_config,
66
- default_db: :test,
67
- }
68
- }
69
- }
70
-
71
- it { expect(shinq.connection(db_name: :test)).to be_a_kind_of(Mysql2::Client) }
72
+ describe '.clear_all_connections!' do
73
+ before do
74
+ shinq.configuration = { db_config: load_database_config, default_db: :test }
75
+ end
76
+
77
+ context 'when there are no connections' do
78
+ it { expect { shinq.clear_all_connections! }.not_to raise_error }
79
+ end
80
+
81
+ context 'when there is a connection' do
82
+ let!(:connection) { shinq.connection(db_name: :test) }
83
+
84
+ it 'closes connection' do
85
+ shinq.clear_all_connections!
86
+ expect(connection.ping).to be false
87
+ end
88
+
89
+ it 'clears connection cache' do
90
+ shinq.clear_all_connections!
91
+ expect(shinq.connection(db_name: :test)).not_to eq connection
92
+ end
72
93
  end
73
94
  end
74
95
 
75
96
  describe ".logger" do
76
- context "when logger is present" do
77
- let(:shinq) { shinq_class }
97
+ context "when logger is not present" do
78
98
  it { expect(shinq.logger).to be nil }
79
99
  end
80
100
 
81
101
  context "when logger is present" do
82
102
  let(:logger) { Logger.new(STDOUT) }
83
- let(:shinq) {
84
- shinq_class.tap {|s|
85
- s.logger = logger
86
- }
87
- }
103
+ before do
104
+ shinq.logger = logger
105
+ end
88
106
 
89
107
  it { expect(shinq.logger).to be logger }
90
108
  end
@@ -1,7 +1,13 @@
1
+ ENV['RAILS_ENV'] ||= 'test'
2
+ $LOAD_PATH << File.expand_path('../helpers', __FILE__)
3
+
1
4
  require 'rspec/mocks/standalone'
2
5
  require 'simplecov'
6
+ require 'erb'
3
7
  require 'yaml'
4
8
  require 'active_support/core_ext/hash'
9
+ require 'mysql2'
10
+ require 'timecop'
5
11
 
6
12
  def load_database_config
7
13
  db_config = YAML.load_file(File.expand_path('./config/database.yml', __dir__)).symbolize_keys
@@ -25,8 +31,19 @@ RSpec.configure do |config|
25
31
  end
26
32
 
27
33
  config.before(:suite) do
34
+ # MySQL on Travis does not have Q4M plugins.
35
+ # We use QUEUE engine and run Q4M specific spec (integration_spec) only when ENV['TRAVIS'] is nil.
36
+ engine = ENV['TRAVIS'] ? 'InnoDB' : 'QUEUE' # Travis MySQL does not have Q4M plugins.
37
+ sql = ERB.new(File.read(File.expand_path('./db/structure.sql.erb', __dir__))).result(binding)
38
+
28
39
  connection = Mysql2::Client.new(load_database_config[:test].merge(flags: Mysql2::Client::MULTI_STATEMENTS))
29
- connection.query(File.read(File.expand_path('./db/structure.sql', __dir__)))
40
+ result = connection.query(sql)
41
+
42
+ while connection.next_result
43
+ connection.store_result
44
+ end
45
+
46
+ connection.close
30
47
  end
31
48
 
32
49
  config.order = :random
metadata CHANGED
@@ -1,29 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: shinq
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.0
4
+ version: 1.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ryoichi SEKIGUCHI
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-06-29 00:00:00.000000000 Z
11
+ date: 2020-10-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - "~>"
17
+ - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: '1.7'
19
+ version: '0'
20
20
  type: :development
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - "~>"
24
+ - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: '1.7'
26
+ version: '0'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: rake
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -108,6 +108,20 @@ dependencies:
108
108
  - - ">="
109
109
  - !ruby/object:Gem::Version
110
110
  version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: timecop
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
111
125
  - !ruby/object:Gem::Dependency
112
126
  name: mysql2
113
127
  requirement: !ruby/object:Gem::Requirement
@@ -117,7 +131,7 @@ dependencies:
117
131
  version: 0.3.16
118
132
  - - "<"
119
133
  - !ruby/object:Gem::Version
120
- version: '0.5'
134
+ version: '0.6'
121
135
  type: :runtime
122
136
  prerelease: false
123
137
  version_requirements: !ruby/object:Gem::Requirement
@@ -127,7 +141,7 @@ dependencies:
127
141
  version: 0.3.16
128
142
  - - "<"
129
143
  - !ruby/object:Gem::Version
130
- version: '0.5'
144
+ version: '0.6'
131
145
  - !ruby/object:Gem::Dependency
132
146
  name: sql-maker
133
147
  requirement: !ruby/object:Gem::Requirement
@@ -146,30 +160,42 @@ dependencies:
146
160
  name: activesupport
147
161
  requirement: !ruby/object:Gem::Requirement
148
162
  requirements:
149
- - - "~>"
163
+ - - ">="
150
164
  - !ruby/object:Gem::Version
151
165
  version: 4.2.0
166
+ - - "<"
167
+ - !ruby/object:Gem::Version
168
+ version: '6'
152
169
  type: :runtime
153
170
  prerelease: false
154
171
  version_requirements: !ruby/object:Gem::Requirement
155
172
  requirements:
156
- - - "~>"
173
+ - - ">="
157
174
  - !ruby/object:Gem::Version
158
175
  version: 4.2.0
176
+ - - "<"
177
+ - !ruby/object:Gem::Version
178
+ version: '6'
159
179
  - !ruby/object:Gem::Dependency
160
180
  name: activejob
161
181
  requirement: !ruby/object:Gem::Requirement
162
182
  requirements:
163
- - - "~>"
183
+ - - ">="
164
184
  - !ruby/object:Gem::Version
165
185
  version: 4.2.0
186
+ - - "<"
187
+ - !ruby/object:Gem::Version
188
+ version: '6'
166
189
  type: :runtime
167
190
  prerelease: false
168
191
  version_requirements: !ruby/object:Gem::Requirement
169
192
  requirements:
170
- - - "~>"
193
+ - - ">="
171
194
  - !ruby/object:Gem::Version
172
195
  version: 4.2.0
196
+ - - "<"
197
+ - !ruby/object:Gem::Version
198
+ version: '6'
173
199
  - !ruby/object:Gem::Dependency
174
200
  name: serverengine
175
201
  requirement: !ruby/object:Gem::Requirement
@@ -214,8 +240,11 @@ files:
214
240
  - lib/shinq/statistics.rb
215
241
  - shinq.gemspec
216
242
  - spec/config/database.yml
217
- - spec/db/structure.sql
243
+ - spec/db/structure.sql.erb
244
+ - spec/helpers/my_worker.rb
218
245
  - spec/integration_spec.rb
246
+ - spec/shinq/cli_spec.rb
247
+ - spec/shinq/client_spec.rb
219
248
  - spec/shinq/configuration_spec.rb
220
249
  - spec/shinq_spec.rb
221
250
  - spec/spec_helper.rb
@@ -238,15 +267,17 @@ required_rubygems_version: !ruby/object:Gem::Requirement
238
267
  - !ruby/object:Gem::Version
239
268
  version: '0'
240
269
  requirements: []
241
- rubyforge_project:
242
- rubygems_version: 2.6.8
270
+ rubygems_version: 3.0.3
243
271
  signing_key:
244
272
  specification_version: 4
245
273
  summary: Worker and enqueuer for Q4M using the interface of ActiveJob.
246
274
  test_files:
247
275
  - spec/config/database.yml
248
- - spec/db/structure.sql
276
+ - spec/db/structure.sql.erb
277
+ - spec/helpers/my_worker.rb
249
278
  - spec/integration_spec.rb
279
+ - spec/shinq/cli_spec.rb
280
+ - spec/shinq/client_spec.rb
250
281
  - spec/shinq/configuration_spec.rb
251
282
  - spec/shinq_spec.rb
252
283
  - spec/spec_helper.rb
@@ -1,6 +0,0 @@
1
- DROP TABLE IF EXISTS `queue_test`;
2
- CREATE TABLE `queue_test` (
3
- `job_id` varchar(255) NOT NULL,
4
- `title` varchar(255),
5
- `enqueued_at` datetime NOT NULL
6
- ) ENGINE=QUEUE;