queue_classic_pg2 3.2.0.RC1

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.
Files changed (40) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +9 -0
  3. data/.travis.yml +19 -0
  4. data/CONTRIBUTING.md +17 -0
  5. data/Gemfile +9 -0
  6. data/LICENSE.txt +20 -0
  7. data/README.md +326 -0
  8. data/Rakefile +14 -0
  9. data/changelog +146 -0
  10. data/lib/generators/queue_classic/install_generator.rb +36 -0
  11. data/lib/generators/queue_classic/templates/add_queue_classic.rb +9 -0
  12. data/lib/generators/queue_classic/templates/update_queue_classic_3_0_0.rb +9 -0
  13. data/lib/generators/queue_classic/templates/update_queue_classic_3_0_2.rb +11 -0
  14. data/lib/generators/queue_classic/templates/update_queue_classic_3_1_0.rb +9 -0
  15. data/lib/queue_classic/config.rb +85 -0
  16. data/lib/queue_classic/conn_adapter.rb +111 -0
  17. data/lib/queue_classic/queue.rb +119 -0
  18. data/lib/queue_classic/railtie.rb +9 -0
  19. data/lib/queue_classic/setup.rb +58 -0
  20. data/lib/queue_classic/tasks.rb +49 -0
  21. data/lib/queue_classic/version.rb +3 -0
  22. data/lib/queue_classic/worker.rb +166 -0
  23. data/lib/queue_classic.rb +122 -0
  24. data/queue_classic.gemspec +24 -0
  25. data/sql/create_table.sql +25 -0
  26. data/sql/ddl.sql +78 -0
  27. data/sql/downgrade_from_3_0_0.sql +2 -0
  28. data/sql/downgrade_from_3_1_0.sql +1 -0
  29. data/sql/drop_ddl.sql +3 -0
  30. data/sql/update_to_3_0_0.sql +17 -0
  31. data/sql/update_to_3_1_0.sql +9 -0
  32. data/test/benchmark_test.rb +39 -0
  33. data/test/config_test.rb +121 -0
  34. data/test/helper.rb +61 -0
  35. data/test/helper.sql +25 -0
  36. data/test/lib/queue_classic_rails_connection_test.rb +43 -0
  37. data/test/lib/queue_classic_test.rb +42 -0
  38. data/test/queue_test.rb +208 -0
  39. data/test/worker_test.rb +219 -0
  40. metadata +112 -0
@@ -0,0 +1,122 @@
1
+ require_relative "queue_classic/config"
2
+
3
+ module QC
4
+ extend QC::Config
5
+
6
+ # Assign constants for backwards compatibility.
7
+ # They should no longer be used. Prefer the corresponding methods.
8
+ # See +QC::Config+ for more details.
9
+ DEPRECATED_CONSTANTS = {
10
+ :APP_NAME => :app_name,
11
+ :WAIT_TIME => :wait_time,
12
+ :TABLE_NAME => :table_name,
13
+ :QUEUE => :queue,
14
+ :QUEUES => :queues,
15
+ :TOP_BOUND => :top_bound,
16
+ :FORK_WORKER => :fork_worker?,
17
+ }
18
+
19
+ def self.const_missing(const_name)
20
+ if DEPRECATED_CONSTANTS.key? const_name
21
+ config_method = DEPRECATED_CONSTANTS[const_name]
22
+ $stderr.puts <<-MSG
23
+ The constant QC::#{const_name} is deprecated and will be removed in the future.
24
+ Please use the method QC.#{config_method} instead.
25
+ MSG
26
+ QC.public_send config_method
27
+ else
28
+ super
29
+ end
30
+ end
31
+
32
+ # Defer method calls on the QC module to the
33
+ # default queue. This facilitates QC.enqueue()
34
+ def self.method_missing(sym, *args, &block)
35
+ if default_queue.respond_to? sym
36
+ default_queue.public_send(sym, *args, &block)
37
+ else
38
+ super
39
+ end
40
+ end
41
+
42
+ # Ensure QC.respond_to?(:enqueue) equals true (ruby 1.9 only)
43
+ def self.respond_to_missing?(method_name, include_private = false)
44
+ default_queue.respond_to?(method_name)
45
+ end
46
+
47
+ def self.has_connection?
48
+ !default_conn_adapter.nil?
49
+ end
50
+
51
+ def self.default_conn_adapter
52
+ t = Thread.current
53
+ return t[:qc_conn_adapter] if t[:qc_conn_adapter]
54
+ adapter = if rails_connection_sharing_enabled?
55
+ ConnAdapter.new(ActiveRecord::Base.connection.raw_connection)
56
+ else
57
+ ConnAdapter.new
58
+ end
59
+
60
+ t[:qc_conn_adapter] = adapter
61
+ end
62
+
63
+ def self.default_conn_adapter=(conn)
64
+ Thread.current[:qc_conn_adapter] = conn
65
+ end
66
+
67
+ def self.log_yield(data)
68
+ t0 = Time.now
69
+ begin
70
+ yield
71
+ rescue => e
72
+ log({:at => "error", :error => e.inspect}.merge(data))
73
+ raise
74
+ ensure
75
+ t = Integer((Time.now - t0)*1000)
76
+ log(data.merge(:elapsed => t)) unless e
77
+ end
78
+ end
79
+
80
+ def self.log(data)
81
+ result = nil
82
+ data = {:lib => "queue-classic"}.merge(data)
83
+ if block_given?
84
+ result = yield
85
+ data.merge(:elapsed => Integer((Time.now - t0)*1000))
86
+ end
87
+ data.reduce(out=String.new) do |s, tup|
88
+ s << [tup.first, tup.last].join("=") << " "
89
+ end
90
+ puts(out) if ENV["DEBUG"]
91
+ return result
92
+ end
93
+
94
+ def self.measure(data)
95
+ if ENV['QC_MEASURE']
96
+ $stdout.puts("measure#qc.#{data}")
97
+ end
98
+ end
99
+
100
+ # This will unlock all jobs any postgres' PID that is not existing anymore
101
+ # to prevent any infinitely locked jobs
102
+ def self.unlock_jobs_of_dead_workers
103
+ pid_column = default_conn_adapter.server_version < 90200 ? "procpid" : "pid"
104
+ default_conn_adapter.execute("UPDATE #{QC.table_name} SET locked_at = NULL, locked_by = NULL WHERE locked_by NOT IN (SELECT #{pid_column} FROM pg_stat_activity);")
105
+ end
106
+
107
+ # private class methods
108
+ class << self
109
+ private
110
+
111
+ def rails_connection_sharing_enabled?
112
+ enabled = ENV.fetch('QC_RAILS_DATABASE', 'true') != 'false'
113
+ return false unless enabled
114
+ return Object.const_defined?("ActiveRecord") && ActiveRecord::Base.respond_to?("connection")
115
+ end
116
+ end
117
+ end
118
+
119
+ require_relative "queue_classic/queue"
120
+ require_relative "queue_classic/worker"
121
+ require_relative "queue_classic/setup"
122
+ require_relative "queue_classic/railtie" if defined?(Rails)
@@ -0,0 +1,24 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'queue_classic/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "queue_classic_pg2"
8
+ spec.email = "r@32k.io"
9
+ spec.version = QC::VERSION
10
+ spec.description = "Support for Postgres2 : 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."
11
+ spec.summary = "Simple, efficient worker queue for Ruby & PostgreSQL."
12
+ spec.authors = ["Ryan Smith (♠ ace hacker)"]
13
+ spec.homepage = "https://github.com/PatrickLef/queue_classic"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.require_paths = %w[lib]
22
+
23
+ spec.add_dependency "pg", ">= 0.17", "< 2.0"
24
+ end
@@ -0,0 +1,25 @@
1
+ DO $$ BEGIN
2
+
3
+ CREATE TABLE queue_classic_jobs (
4
+ id bigserial PRIMARY KEY,
5
+ q_name text NOT NULL CHECK (length(q_name) > 0),
6
+ method text NOT NULL CHECK (length(method) > 0),
7
+ args text NOT NULL,
8
+ locked_at timestamptz,
9
+ locked_by integer,
10
+ created_at timestamptz DEFAULT now(),
11
+ scheduled_at timestamptz DEFAULT now()
12
+ );
13
+
14
+ -- If jsonb type is available, use it for the args column
15
+ IF EXISTS (SELECT 1 FROM pg_type WHERE typname = 'jsonb') THEN
16
+ ALTER TABLE queue_classic_jobs ALTER COLUMN args TYPE jsonb USING args::jsonb;
17
+ -- Otherwise, use json type for the args column if available
18
+ ELSIF EXISTS (SELECT 1 FROM pg_type WHERE typname = 'json') THEN
19
+ ALTER TABLE queue_classic_jobs ALTER COLUMN args TYPE json USING args::json;
20
+ END IF;
21
+
22
+ END $$ LANGUAGE plpgsql;
23
+
24
+ CREATE INDEX idx_qc_on_name_only_unlocked ON queue_classic_jobs (q_name, id) WHERE locked_at IS NULL;
25
+ CREATE INDEX idx_qc_on_scheduled_at_only_unlocked ON queue_classic_jobs (scheduled_at, id) WHERE locked_at IS NULL;
data/sql/ddl.sql ADDED
@@ -0,0 +1,78 @@
1
+ -- We are declaring the return type to be queue_classic_jobs.
2
+ -- This is ok since I am assuming that all of the users added queues will
3
+ -- have identical columns to queue_classic_jobs.
4
+ -- When QC supports queues with columns other than the default, we will have to change this.
5
+
6
+ CREATE OR REPLACE FUNCTION lock_head(q_name varchar, top_boundary integer)
7
+ RETURNS SETOF queue_classic_jobs AS $$
8
+ DECLARE
9
+ unlocked bigint;
10
+ relative_top integer;
11
+ job_count integer;
12
+ BEGIN
13
+ -- The purpose is to release contention for the first spot in the table.
14
+ -- The select count(*) is going to slow down dequeue performance but allow
15
+ -- for more workers. Would love to see some optimization here...
16
+
17
+ EXECUTE 'SELECT count(*) FROM '
18
+ || '(SELECT * FROM queue_classic_jobs '
19
+ || ' WHERE locked_at IS NULL'
20
+ || ' AND q_name = '
21
+ || quote_literal(q_name)
22
+ || ' AND scheduled_at <= '
23
+ || quote_literal(now())
24
+ || ' LIMIT '
25
+ || quote_literal(top_boundary)
26
+ || ') limited'
27
+ INTO job_count;
28
+
29
+ SELECT TRUNC(random() * (top_boundary - 1))
30
+ INTO relative_top;
31
+
32
+ IF job_count < top_boundary THEN
33
+ relative_top = 0;
34
+ END IF;
35
+
36
+ LOOP
37
+ BEGIN
38
+ EXECUTE 'SELECT id FROM queue_classic_jobs '
39
+ || ' WHERE locked_at IS NULL'
40
+ || ' AND q_name = '
41
+ || quote_literal(q_name)
42
+ || ' AND scheduled_at <= '
43
+ || quote_literal(now())
44
+ || ' ORDER BY id ASC'
45
+ || ' LIMIT 1'
46
+ || ' OFFSET ' || quote_literal(relative_top)
47
+ || ' FOR UPDATE NOWAIT'
48
+ INTO unlocked;
49
+ EXIT;
50
+ EXCEPTION
51
+ WHEN lock_not_available THEN
52
+ -- do nothing. loop again and hope we get a lock
53
+ END;
54
+ END LOOP;
55
+
56
+ RETURN QUERY EXECUTE 'UPDATE queue_classic_jobs '
57
+ || ' SET locked_at = (CURRENT_TIMESTAMP),'
58
+ || ' locked_by = (select pg_backend_pid())'
59
+ || ' WHERE id = $1'
60
+ || ' AND locked_at is NULL'
61
+ || ' RETURNING *'
62
+ USING unlocked;
63
+
64
+ RETURN;
65
+ END $$ LANGUAGE plpgsql;
66
+
67
+ CREATE OR REPLACE FUNCTION lock_head(tname varchar) RETURNS SETOF queue_classic_jobs AS $$ BEGIN
68
+ RETURN QUERY EXECUTE 'SELECT * FROM lock_head($1,10)' USING tname;
69
+ END $$ LANGUAGE plpgsql;
70
+
71
+ -- queue_classic_notify function and trigger
72
+ CREATE FUNCTION queue_classic_notify() RETURNS TRIGGER AS $$ BEGIN
73
+ perform pg_notify(new.q_name, ''); RETURN NULL;
74
+ END $$ LANGUAGE plpgsql;
75
+
76
+ CREATE TRIGGER queue_classic_notify
77
+ AFTER INSERT ON queue_classic_jobs FOR EACH ROW
78
+ EXECUTE PROCEDURE queue_classic_notify();
@@ -0,0 +1,2 @@
1
+ ALTER TABLE queue_classic_jobs DROP COLUMN locked_by;
2
+ ALTER TABLE queue_classic_jobs DROP COLUMN created_at;
@@ -0,0 +1 @@
1
+ ALTER TABLE queue_classic_jobs DROP COLUMN scheduled_at;
data/sql/drop_ddl.sql ADDED
@@ -0,0 +1,3 @@
1
+ DROP FUNCTION IF EXISTS lock_head(tname varchar);
2
+ DROP FUNCTION IF EXISTS lock_head(q_name varchar, top_boundary integer);
3
+ DROP FUNCTION IF EXISTS queue_classic_notify() cascade;
@@ -0,0 +1,17 @@
1
+ DO $$DECLARE r record;
2
+ BEGIN
3
+ BEGIN
4
+ ALTER TABLE queue_classic_jobs ADD COLUMN created_at timestamptz DEFAULT now();
5
+ EXCEPTION
6
+ WHEN duplicate_column THEN RAISE NOTICE 'column created_at already exists in queue_classic_jobs.';
7
+ END;
8
+ END$$;
9
+
10
+ DO $$DECLARE r record;
11
+ BEGIN
12
+ BEGIN
13
+ ALTER TABLE queue_classic_jobs ADD COLUMN locked_by integer;
14
+ EXCEPTION
15
+ WHEN duplicate_column THEN RAISE NOTICE 'column locked_by already exists in queue_classic_jobs.';
16
+ END;
17
+ END$$;
@@ -0,0 +1,9 @@
1
+ DO $$DECLARE r record;
2
+ BEGIN
3
+ BEGIN
4
+ ALTER TABLE queue_classic_jobs ADD COLUMN scheduled_at timestamptz DEFAULT now();
5
+ CREATE INDEX idx_qc_on_scheduled_at_only_unlocked ON queue_classic_jobs (scheduled_at, id) WHERE locked_at IS NULL;
6
+ EXCEPTION
7
+ WHEN duplicate_column THEN RAISE NOTICE 'column scheduled_at already exists in queue_classic_jobs.';
8
+ END;
9
+ END$$;
@@ -0,0 +1,39 @@
1
+ require_relative 'helper'
2
+
3
+ if ENV["QC_BENCHMARK"]
4
+ class BenchmarkTest < QCTest
5
+ BENCHMARK_SIZE = Integer(ENV.fetch("QC_BENCHMARK_SIZE", 10_000))
6
+ BENCHMARK_MAX_TIME_DEQUEUE = Integer(ENV.fetch("QC_BENCHMARK_MAX_TIME_DEQUEUE", 30))
7
+ BENCHMARK_MAX_TIME_ENQUEUE = Integer(ENV.fetch("QC_BENCHMARK_MAX_TIME_ENQUEUE", 5))
8
+
9
+ def test_enqueue
10
+ start = Time.now
11
+ BENCHMARK_SIZE.times do
12
+ QC.enqueue("1.odd?")
13
+ end
14
+ assert_equal(BENCHMARK_SIZE, QC.count)
15
+
16
+ elapsed = Time.now - start
17
+ assert_operator(elapsed, :<, BENCHMARK_MAX_TIME_ENQUEUE)
18
+ end
19
+
20
+ def test_dequeue
21
+ worker = QC::Worker.new
22
+ worker.running = true
23
+ BENCHMARK_SIZE.times do
24
+ QC.enqueue("1.odd?")
25
+ end
26
+ assert_equal(BENCHMARK_SIZE, QC.count)
27
+
28
+ start = Time.now
29
+ BENCHMARK_SIZE.times do
30
+ worker.work
31
+ end
32
+ elapsed = Time.now - start
33
+
34
+ assert_equal(0, QC.count)
35
+ assert_operator(elapsed, :<, BENCHMARK_MAX_TIME_DEQUEUE)
36
+ end
37
+
38
+ end
39
+ end
@@ -0,0 +1,121 @@
1
+ require_relative 'helper'
2
+
3
+ class ConfigTest < QCTest
4
+ def setup
5
+ QC.reset_config
6
+ end
7
+
8
+ def teardown
9
+ QC.reset_config
10
+ end
11
+
12
+ def test_app_name_default
13
+ assert_equal "queue_classic", QC.app_name
14
+ end
15
+
16
+ def test_configure_app_name_with_env_var
17
+ with_env "QC_APP_NAME" => "zomg_qc" do
18
+ assert_equal "zomg_qc", QC.app_name
19
+ end
20
+ end
21
+
22
+ def test_wait_time_default
23
+ assert_equal 5, QC.wait_time
24
+ end
25
+
26
+ def test_configure_wait_time_with_env_var
27
+ with_env "QC_LISTEN_TIME" => "7" do
28
+ assert_equal 7, QC.wait_time
29
+ end
30
+ end
31
+
32
+ def test_table_name_default
33
+ assert_equal "queue_classic_jobs", QC.table_name
34
+ end
35
+
36
+ def test_queue_default
37
+ assert_equal "default", QC.queue
38
+ assert_equal "default", QC.default_queue.name
39
+ end
40
+
41
+ def test_configure_queue_with_env_var
42
+ with_env "QUEUE" => "priority" do
43
+ assert_equal "priority", QC.queue
44
+ assert_equal "priority", QC.default_queue.name
45
+ end
46
+ end
47
+
48
+ def test_assign_default_queue
49
+ QC.default_queue = QC::Queue.new "dispensable"
50
+ assert_equal "default", QC.queue
51
+ assert_equal "dispensable", QC.default_queue.name
52
+ end
53
+
54
+ def test_queues_default
55
+ assert_equal [], QC.queues
56
+ end
57
+
58
+ def test_configure_queues_with_env_var
59
+ with_env "QUEUES" => "first,second,third" do
60
+ assert_equal %w(first second third), QC.queues
61
+ end
62
+ end
63
+
64
+ def test_configure_queues_with_whitespace
65
+ with_env "QUEUES" => " one, two, three " do
66
+ assert_equal %w(one two three), QC.queues
67
+ end
68
+ end
69
+
70
+ def test_top_bound_default
71
+ assert_equal 9, QC.top_bound
72
+ end
73
+
74
+ def test_configure_top_bound_with_env_var
75
+ with_env "QC_TOP_BOUND" => "5" do
76
+ assert_equal 5, QC.top_bound
77
+ end
78
+ end
79
+
80
+ def test_fork_worker_default
81
+ refute QC.fork_worker?
82
+ end
83
+
84
+ def test_configure_fork_worker_with_env_var
85
+ with_env "QC_FORK_WORKER" => "yo" do
86
+ assert QC.fork_worker?
87
+ end
88
+ end
89
+
90
+ def test_configuration_constants_are_deprecated
91
+ warning = capture_stderr_output do
92
+ QC::FORK_WORKER
93
+ end
94
+ assert_match "QC::FORK_WORKER is deprecated", warning
95
+ assert_match "QC.fork_worker? instead", warning
96
+ end
97
+
98
+ class TestWorker < QC::Worker; end
99
+
100
+ def test_default_worker_class
101
+ assert_equal QC::Worker, QC.default_worker_class
102
+ end
103
+
104
+ def test_configure_default_worker_class_with_env_var
105
+ if RUBY_VERSION =~ /^1\.9\./
106
+ skip "Kernel.const_get in Ruby 1.9.x does not perform recursive lookups"
107
+ end
108
+ with_env "QC_DEFAULT_WORKER_CLASS" => "ConfigTest::TestWorker" do
109
+ assert_equal TestWorker, QC.default_worker_class
110
+ end
111
+ end
112
+
113
+ def test_assign_default_worker_class
114
+ original_worker = QC.default_worker_class
115
+ QC.default_worker_class = TestWorker
116
+
117
+ assert_equal TestWorker, QC.default_worker_class
118
+ ensure
119
+ QC.default_worker_class = original_worker
120
+ end
121
+ end
data/test/helper.rb ADDED
@@ -0,0 +1,61 @@
1
+ require "bundler"
2
+ Bundler.setup :default, :test
3
+
4
+ ENV["DATABASE_URL"] ||= "postgres:///queue_classic_test"
5
+
6
+ require_relative '../lib/queue_classic'
7
+ require "stringio"
8
+ require "minitest/autorun"
9
+
10
+ class QCTest < Minitest::Test
11
+
12
+ def setup
13
+ init_db
14
+ end
15
+
16
+ def teardown
17
+ QC.delete_all
18
+ end
19
+
20
+ def init_db
21
+ c = QC::ConnAdapter.new
22
+ c.execute("SET client_min_messages TO 'warning'")
23
+ QC::Setup.drop(c.connection)
24
+ QC::Setup.create(c.connection)
25
+ c.execute(File.read('./test/helper.sql'))
26
+ c.disconnect
27
+ end
28
+
29
+ def capture_stderr_output
30
+ original_stderr = $stderr
31
+ $stderr = StringIO.new
32
+ yield
33
+ $stderr.string
34
+ ensure
35
+ $stderr = original_stderr
36
+ end
37
+
38
+ def capture_debug_output
39
+ original_debug = ENV['DEBUG']
40
+ original_stdout = $stdout
41
+
42
+ ENV['DEBUG'] = "true"
43
+ $stdout = StringIO.new
44
+ yield
45
+ $stdout.string
46
+ ensure
47
+ ENV['DEBUG'] = original_debug
48
+ $stdout = original_stdout
49
+ end
50
+
51
+ def with_env(temporary_environment)
52
+ original_environment = {}
53
+ temporary_environment.each do |name, value|
54
+ original_environment[name] = ENV[name]
55
+ ENV[name] = value
56
+ end
57
+ yield
58
+ ensure
59
+ original_environment.each { |name, value| ENV[name] = value }
60
+ end
61
+ end
data/test/helper.sql ADDED
@@ -0,0 +1,25 @@
1
+ DO $$
2
+ -- Set initial sequence to a large number to test the entire toolchain
3
+ -- works on integers with higher bits set.
4
+ DECLARE
5
+ quoted_name text;
6
+ quoted_size text;
7
+ BEGIN
8
+ -- Find the name of the relevant sequence.
9
+ --
10
+ -- pg_get_serial_sequence quotes identifiers as part of its
11
+ -- behavior.
12
+ SELECT name
13
+ INTO STRICT quoted_name
14
+ FROM pg_get_serial_sequence('queue_classic_jobs', 'id') AS name;
15
+
16
+ -- Don't quote, because ALTER SEQUENCE RESTART doesn't like
17
+ -- general literals, only unquoted numeric literals.
18
+ SELECT pow(2, 34)::text AS size
19
+ INTO STRICT quoted_size;
20
+
21
+ EXECUTE 'ALTER SEQUENCE ' || quoted_name ||
22
+ ' RESTART ' || quoted_size || ';';
23
+ END;
24
+ $$;
25
+
@@ -0,0 +1,43 @@
1
+ require File.expand_path("../../helper.rb", __FILE__)
2
+
3
+ class QueueClassicRailsConnectionTest < QCTest
4
+ def before_setup
5
+ Object.send :const_set, :ActiveRecord, Module.new
6
+ ActiveRecord.const_set :Base, Module.new
7
+
8
+ @original_conn_adapter = QC.default_conn_adapter
9
+ QC.default_conn_adapter = nil
10
+ end
11
+
12
+ def after_teardown
13
+ ActiveRecord.send :remove_const, :Base
14
+ Object.send :remove_const, :ActiveRecord
15
+
16
+ QC.default_conn_adapter = @original_conn_adapter
17
+ end
18
+
19
+ def test_uses_active_record_connection_if_exists
20
+ connection = get_connection
21
+ assert connection.verify
22
+ end
23
+
24
+ def test_does_not_use_active_record_connection_if_env_var_set
25
+ with_env 'QC_RAILS_DATABASE' => 'false' do
26
+ connection = get_connection
27
+ assert_raises(MockExpectationError) { connection.verify }
28
+ end
29
+ end
30
+
31
+ private
32
+ def get_connection
33
+ connection = Minitest::Mock.new
34
+ connection.expect(:raw_connection, QC::ConnAdapter.new.connection)
35
+
36
+ ActiveRecord::Base.define_singleton_method(:connection) do
37
+ connection
38
+ end
39
+
40
+ QC.default_conn_adapter
41
+ connection
42
+ end
43
+ end
@@ -0,0 +1,42 @@
1
+ require File.expand_path("../../helper.rb", __FILE__)
2
+
3
+ class QueueClassicTest < QCTest
4
+ def test_only_delegate_calls_to_queue_it_understands
5
+ e = assert_raises(NoMethodError) do
6
+ QC.probably_not
7
+ end
8
+ assert_match "undefined method `probably_not' for QC:Module", e.message
9
+ end
10
+
11
+ def test_default_conn_adapter_default_value
12
+ assert(QC.default_conn_adapter.is_a?(QC::ConnAdapter))
13
+ end
14
+
15
+ def test_assigning_a_default_conn_adapter
16
+ original_conn_adapter = QC.default_conn_adapter
17
+ connection = QC::ConnAdapter.new
18
+ QC.default_conn_adapter = connection
19
+ assert_equal(QC.default_conn_adapter, connection)
20
+ ensure
21
+ QC.default_conn_adapter = original_conn_adapter
22
+ end
23
+
24
+ def test_unlock_jobs_of_dead_workers
25
+ # Insert a locked job
26
+ adapter = QC::ConnAdapter.new
27
+ query = "INSERT INTO #{QC.table_name} (q_name, method, args, locked_by, locked_at) VALUES ('whatever', 'Kernel.puts', '[\"ok?\"]', 0, (CURRENT_TIMESTAMP))"
28
+ adapter.execute(query)
29
+
30
+ # We should have no unlocked jobs
31
+ query_locked_jobs = "SELECT * FROM #{QC.table_name} WHERE locked_at IS NULL"
32
+ res = adapter.connection.exec(query_locked_jobs)
33
+ assert_equal(0, res.count)
34
+
35
+ # Unlock the job
36
+ QC.unlock_jobs_of_dead_workers
37
+
38
+ # We should have an unlocked job now
39
+ res = adapter.connection.exec(query_locked_jobs)
40
+ assert_equal(1, res.count)
41
+ end
42
+ end