shinq 0.8.0 → 1.0.2

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