sidekiq 8.1.0 → 8.1.1
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 +4 -4
- data/Changes.md +14 -0
- data/bin/tui +5 -0
- data/lib/active_job/queue_adapters/sidekiq_adapter.rb +5 -2
- data/lib/generators/sidekiq/job_generator.rb +15 -3
- data/lib/sidekiq/api.rb +84 -41
- data/lib/sidekiq/test_api.rb +331 -0
- data/lib/sidekiq/testing/inline.rb +2 -30
- data/lib/sidekiq/testing.rb +2 -334
- data/lib/sidekiq/tui.rb +1019 -0
- data/lib/sidekiq/version.rb +1 -1
- data/lib/sidekiq/web/application.rb +2 -2
- data/lib/sidekiq/web/helpers.rb +11 -0
- data/lib/sidekiq.rb +7 -0
- data/web/assets/javascripts/application.js +1 -1
- data/web/views/busy.html.erb +1 -1
- metadata +5 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4fb090d79e2cf2b320fdd47ee55a574a68a46b77a23db27b849ddb6f54acb00a
|
|
4
|
+
data.tar.gz: 4863ef28ecdd8ad2cd868caf2f47cc9302b8eb88f4ff9da4f2b5641beca50d88
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4ba71a2c43cec613337119cd29830339e213a8f876db4db84c62ac67890ee020ab841a4f7fe478f7f0fcd135571b885e54c9af12998ea6947ac51b543d52b42c
|
|
7
|
+
data.tar.gz: 9cabfc243aacec3c57c697b83374a5b934cc985c1e4ee86185a6faba7fe18f6540e3fa0b203c5dbfc0e22c03513f66e973be4152d8c8e12db29e97e21a94360f
|
data/Changes.md
CHANGED
|
@@ -2,6 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
[Sidekiq Changes](https://github.com/sidekiq/sidekiq/blob/main/Changes.md) | [Sidekiq Pro Changes](https://github.com/sidekiq/sidekiq/blob/main/Pro-Changes.md) | [Sidekiq Enterprise Changes](https://github.com/sidekiq/sidekiq/blob/main/Ent-Changes.md)
|
|
4
4
|
|
|
5
|
+
8.1.1
|
|
6
|
+
----------
|
|
7
|
+
|
|
8
|
+
- Add new `Sidekiq.testing!(mode)` API [#6931]
|
|
9
|
+
Requiring code should not enable process-wide changes.
|
|
10
|
+
```ruby
|
|
11
|
+
# Old, implicit
|
|
12
|
+
require "sidekiq/testing"
|
|
13
|
+
# New, more explicit
|
|
14
|
+
Sidekiq.testing!(:fake)
|
|
15
|
+
```
|
|
16
|
+
- Fix race condition with Stop button in UI [#6935]
|
|
17
|
+
- Fix javascript error handler [#6893]
|
|
18
|
+
|
|
5
19
|
8.1.0
|
|
6
20
|
----------
|
|
7
21
|
|
data/bin/tui
ADDED
|
@@ -61,10 +61,13 @@ begin
|
|
|
61
61
|
|
|
62
62
|
# @api private
|
|
63
63
|
def enqueue(job)
|
|
64
|
-
|
|
64
|
+
# NB: Active Job only serializes keys it recognizes. We
|
|
65
|
+
# cannot set arbitrary key/values here.
|
|
66
|
+
wrapper = Sidekiq::ActiveJob::Wrapper.set(
|
|
65
67
|
wrapped: job.class,
|
|
66
68
|
queue: job.queue_name
|
|
67
|
-
)
|
|
69
|
+
)
|
|
70
|
+
job.provider_job_id = wrapper.perform_async(job.serialize)
|
|
68
71
|
end
|
|
69
72
|
|
|
70
73
|
# @api private
|
|
@@ -14,7 +14,10 @@ module Sidekiq
|
|
|
14
14
|
end
|
|
15
15
|
|
|
16
16
|
def create_job_file
|
|
17
|
-
template
|
|
17
|
+
template(
|
|
18
|
+
"job.rb.erb",
|
|
19
|
+
File.join(jobs_directory, class_path, "#{file_name}_job.rb")
|
|
20
|
+
)
|
|
18
21
|
end
|
|
19
22
|
|
|
20
23
|
def create_test_file
|
|
@@ -31,7 +34,8 @@ module Sidekiq
|
|
|
31
34
|
|
|
32
35
|
def create_job_spec
|
|
33
36
|
template_file = File.join(
|
|
34
|
-
"spec
|
|
37
|
+
"spec",
|
|
38
|
+
jobs_directory.gsub("app/", ""),
|
|
35
39
|
class_path,
|
|
36
40
|
"#{file_name}_job_spec.rb"
|
|
37
41
|
)
|
|
@@ -40,7 +44,8 @@ module Sidekiq
|
|
|
40
44
|
|
|
41
45
|
def create_job_test
|
|
42
46
|
template_file = File.join(
|
|
43
|
-
"test
|
|
47
|
+
"test",
|
|
48
|
+
jobs_directory.gsub("app/", ""),
|
|
44
49
|
class_path,
|
|
45
50
|
"#{file_name}_job_test.rb"
|
|
46
51
|
)
|
|
@@ -54,6 +59,13 @@ module Sidekiq
|
|
|
54
59
|
def test_framework
|
|
55
60
|
::Rails.application.config.generators.options[:rails][:test_framework]
|
|
56
61
|
end
|
|
62
|
+
|
|
63
|
+
# Can be set in an initializer or in application configuration
|
|
64
|
+
# with Rails.application.config.generators.options[:sidekiq][:jobs_directory] = "app/jobs"
|
|
65
|
+
# to change the directory that the job files are generated in to.
|
|
66
|
+
def jobs_directory
|
|
67
|
+
::Rails.application.config.generators.options[:sidekiq].fetch(:jobs_directory, "app/sidekiq")
|
|
68
|
+
end
|
|
57
69
|
end
|
|
58
70
|
end
|
|
59
71
|
end
|
data/lib/sidekiq/api.rb
CHANGED
|
@@ -17,12 +17,37 @@ require "sidekiq/metrics/query"
|
|
|
17
17
|
#
|
|
18
18
|
|
|
19
19
|
module Sidekiq
|
|
20
|
+
module ApiUtils
|
|
21
|
+
# @api private
|
|
22
|
+
# Calculate the latency in seconds for a job based on its enqueued timestamp
|
|
23
|
+
# @param job [Hash] the job hash
|
|
24
|
+
# @return [Float] latency in seconds
|
|
25
|
+
def calculate_latency(job)
|
|
26
|
+
timestamp = job["enqueued_at"] || job["created_at"]
|
|
27
|
+
return 0.0 unless timestamp
|
|
28
|
+
|
|
29
|
+
if timestamp.is_a?(Float)
|
|
30
|
+
# old format
|
|
31
|
+
Time.now.to_f - timestamp
|
|
32
|
+
else
|
|
33
|
+
now = ::Process.clock_gettime(::Process::CLOCK_REALTIME, :millisecond)
|
|
34
|
+
(now - timestamp) / 1000.0
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
20
39
|
# Retrieve runtime statistics from Redis regarding
|
|
21
40
|
# this Sidekiq cluster.
|
|
22
41
|
#
|
|
23
42
|
# stat = Sidekiq::Stats.new
|
|
24
43
|
# stat.processed
|
|
25
44
|
class Stats
|
|
45
|
+
QueueSummary = Data.define(:name, :size, :latency, :paused) do
|
|
46
|
+
alias_method :paused?, :paused
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
include ApiUtils
|
|
50
|
+
|
|
26
51
|
def initialize
|
|
27
52
|
fetch_stats_fast!
|
|
28
53
|
end
|
|
@@ -63,6 +88,7 @@ module Sidekiq
|
|
|
63
88
|
stat :default_queue_latency
|
|
64
89
|
end
|
|
65
90
|
|
|
91
|
+
# @return [Hash{String => Integer}] a hash of queue names to their lengths
|
|
66
92
|
def queues
|
|
67
93
|
Sidekiq.redis do |conn|
|
|
68
94
|
queues = conn.sscan("queues").to_a
|
|
@@ -78,6 +104,41 @@ module Sidekiq
|
|
|
78
104
|
end
|
|
79
105
|
end
|
|
80
106
|
|
|
107
|
+
# More detailed information about each queue: name, size, latency, paused status
|
|
108
|
+
# @return [Array<QueueSummary>]
|
|
109
|
+
def queue_summaries
|
|
110
|
+
Sidekiq.redis do |conn|
|
|
111
|
+
queues = conn.sscan("queues").to_a
|
|
112
|
+
return [] if queues.empty?
|
|
113
|
+
|
|
114
|
+
results = conn.pipelined { |pipeline|
|
|
115
|
+
queues.each do |queue|
|
|
116
|
+
pipeline.llen("queue:#{queue}")
|
|
117
|
+
pipeline.lindex("queue:#{queue}", -1)
|
|
118
|
+
pipeline.sismember("paused", queue)
|
|
119
|
+
end
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
queue_summaries = []
|
|
123
|
+
queues.each_with_index do |name, idx|
|
|
124
|
+
size = results[idx * 3]
|
|
125
|
+
last_item = results[idx * 3 + 1]
|
|
126
|
+
paused = results[idx * 3 + 2] > 0
|
|
127
|
+
|
|
128
|
+
latency = if last_item
|
|
129
|
+
job = Sidekiq.load_json(last_item)
|
|
130
|
+
calculate_latency(job)
|
|
131
|
+
else
|
|
132
|
+
0.0
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
queue_summaries << QueueSummary.new(name:, size:, latency:, paused:)
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
queue_summaries.sort_by { |qd| -qd.size }
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
|
|
81
142
|
# O(1) redis calls
|
|
82
143
|
# @api private
|
|
83
144
|
def fetch_stats_fast!
|
|
@@ -100,19 +161,7 @@ module Sidekiq
|
|
|
100
161
|
{}
|
|
101
162
|
end
|
|
102
163
|
|
|
103
|
-
|
|
104
|
-
if enqueued_at
|
|
105
|
-
if enqueued_at.is_a?(Float)
|
|
106
|
-
# old format
|
|
107
|
-
now = Time.now.to_f
|
|
108
|
-
now - enqueued_at
|
|
109
|
-
else
|
|
110
|
-
now = ::Process.clock_gettime(::Process::CLOCK_REALTIME, :millisecond)
|
|
111
|
-
(now - enqueued_at) / 1000.0
|
|
112
|
-
end
|
|
113
|
-
else
|
|
114
|
-
0.0
|
|
115
|
-
end
|
|
164
|
+
calculate_latency(job)
|
|
116
165
|
else
|
|
117
166
|
0.0
|
|
118
167
|
end
|
|
@@ -235,6 +284,7 @@ module Sidekiq
|
|
|
235
284
|
# end
|
|
236
285
|
class Queue
|
|
237
286
|
include Enumerable
|
|
287
|
+
include ApiUtils
|
|
238
288
|
|
|
239
289
|
##
|
|
240
290
|
# Fetch all known queues within Redis.
|
|
@@ -245,6 +295,7 @@ module Sidekiq
|
|
|
245
295
|
end
|
|
246
296
|
|
|
247
297
|
attr_reader :name
|
|
298
|
+
alias_method :id, :name
|
|
248
299
|
|
|
249
300
|
# @param name [String] the name of the queue
|
|
250
301
|
def initialize(name = "default")
|
|
@@ -277,19 +328,7 @@ module Sidekiq
|
|
|
277
328
|
return 0.0 unless entry
|
|
278
329
|
|
|
279
330
|
job = Sidekiq.load_json(entry)
|
|
280
|
-
|
|
281
|
-
if enqueued_at
|
|
282
|
-
if enqueued_at.is_a?(Float)
|
|
283
|
-
# old format
|
|
284
|
-
now = Time.now.to_f
|
|
285
|
-
now - enqueued_at
|
|
286
|
-
else
|
|
287
|
-
now = ::Process.clock_gettime(::Process::CLOCK_REALTIME, :millisecond)
|
|
288
|
-
(now - enqueued_at) / 1000.0
|
|
289
|
-
end
|
|
290
|
-
else
|
|
291
|
-
0.0
|
|
292
|
-
end
|
|
331
|
+
calculate_latency(job)
|
|
293
332
|
end
|
|
294
333
|
|
|
295
334
|
def each
|
|
@@ -352,6 +391,8 @@ module Sidekiq
|
|
|
352
391
|
# The job should be considered immutable but may be
|
|
353
392
|
# removed from the queue via JobRecord#delete.
|
|
354
393
|
class JobRecord
|
|
394
|
+
include ApiUtils
|
|
395
|
+
|
|
355
396
|
# the parsed Hash of job data
|
|
356
397
|
# @!attribute [r] Item
|
|
357
398
|
attr_reader :item
|
|
@@ -478,17 +519,7 @@ module Sidekiq
|
|
|
478
519
|
end
|
|
479
520
|
|
|
480
521
|
def latency
|
|
481
|
-
|
|
482
|
-
if timestamp
|
|
483
|
-
if timestamp.is_a?(Float)
|
|
484
|
-
# old format
|
|
485
|
-
Time.now.to_f - timestamp
|
|
486
|
-
else
|
|
487
|
-
(::Process.clock_gettime(::Process::CLOCK_REALTIME, :millisecond) - timestamp) / 1000.0
|
|
488
|
-
end
|
|
489
|
-
else
|
|
490
|
-
0.0
|
|
491
|
-
end
|
|
522
|
+
calculate_latency(@item)
|
|
492
523
|
end
|
|
493
524
|
|
|
494
525
|
# Remove this job from the queue
|
|
@@ -553,17 +584,24 @@ module Sidekiq
|
|
|
553
584
|
# could be the scheduled time for it to run (e.g. scheduled set),
|
|
554
585
|
# or the expiration date after which the entry should be deleted (e.g. dead set).
|
|
555
586
|
class SortedEntry < JobRecord
|
|
556
|
-
attr_reader :score
|
|
557
587
|
attr_reader :parent
|
|
558
588
|
|
|
559
589
|
# :nodoc:
|
|
560
590
|
# @api private
|
|
561
591
|
def initialize(parent, score, item)
|
|
562
592
|
super(item)
|
|
563
|
-
@score =
|
|
593
|
+
@score = score
|
|
564
594
|
@parent = parent
|
|
565
595
|
end
|
|
566
596
|
|
|
597
|
+
def score
|
|
598
|
+
Float(@score)
|
|
599
|
+
end
|
|
600
|
+
|
|
601
|
+
def id
|
|
602
|
+
"#{@score}|#{item["jid"]}"
|
|
603
|
+
end
|
|
604
|
+
|
|
567
605
|
# The timestamp associated with this entry
|
|
568
606
|
def at
|
|
569
607
|
Time.at(score).utc
|
|
@@ -574,7 +612,7 @@ module Sidekiq
|
|
|
574
612
|
if @value
|
|
575
613
|
@parent.delete_by_value(@parent.name, @value)
|
|
576
614
|
else
|
|
577
|
-
@parent.delete_by_jid(score, jid)
|
|
615
|
+
@parent.delete_by_jid(@score, jid)
|
|
578
616
|
end
|
|
579
617
|
end
|
|
580
618
|
|
|
@@ -583,7 +621,7 @@ module Sidekiq
|
|
|
583
621
|
# @param at [Time] the new timestamp for this job
|
|
584
622
|
def reschedule(at)
|
|
585
623
|
Sidekiq.redis do |conn|
|
|
586
|
-
conn.zincrby(@parent.name, at.to_f -
|
|
624
|
+
conn.zincrby(@parent.name, at.to_f - score, Sidekiq.dump_json(@item))
|
|
587
625
|
end
|
|
588
626
|
end
|
|
589
627
|
|
|
@@ -1088,6 +1126,7 @@ module Sidekiq
|
|
|
1088
1126
|
def identity
|
|
1089
1127
|
self["identity"]
|
|
1090
1128
|
end
|
|
1129
|
+
alias_method :id, :identity
|
|
1091
1130
|
|
|
1092
1131
|
# deprecated, use capsules below
|
|
1093
1132
|
def queues
|
|
@@ -1161,6 +1200,10 @@ module Sidekiq
|
|
|
1161
1200
|
self["quiet"] == "true"
|
|
1162
1201
|
end
|
|
1163
1202
|
|
|
1203
|
+
def leader?
|
|
1204
|
+
Sidekiq.redis { |c| c.get("dear-leader") == identity }
|
|
1205
|
+
end
|
|
1206
|
+
|
|
1164
1207
|
private
|
|
1165
1208
|
|
|
1166
1209
|
def signal(sig)
|
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "securerandom"
|
|
4
|
+
require "sidekiq"
|
|
5
|
+
|
|
6
|
+
module Sidekiq
|
|
7
|
+
class Testing
|
|
8
|
+
class TestModeAlreadySetError < RuntimeError; end
|
|
9
|
+
class << self
|
|
10
|
+
attr_accessor :__global_test_mode
|
|
11
|
+
|
|
12
|
+
# Calling without a block sets the global test mode, affecting
|
|
13
|
+
# all threads. Calling with a block only affects the current Thread.
|
|
14
|
+
def __set_test_mode(mode)
|
|
15
|
+
if block_given?
|
|
16
|
+
# Reentrant testing modes will lead to a rat's nest of code which is
|
|
17
|
+
# hard to reason about. You can set the testing mode once globally and
|
|
18
|
+
# you can override that global setting once per-thread.
|
|
19
|
+
raise TestModeAlreadySetError, "Nesting test modes is not supported" if __local_test_mode
|
|
20
|
+
|
|
21
|
+
self.__local_test_mode = mode
|
|
22
|
+
begin
|
|
23
|
+
yield
|
|
24
|
+
ensure
|
|
25
|
+
self.__local_test_mode = nil
|
|
26
|
+
end
|
|
27
|
+
else
|
|
28
|
+
self.__global_test_mode = mode
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def __test_mode
|
|
33
|
+
__local_test_mode || __global_test_mode
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def __local_test_mode
|
|
37
|
+
Thread.current[:__sidekiq_test_mode]
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def __local_test_mode=(value)
|
|
41
|
+
Thread.current[:__sidekiq_test_mode] = value
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def disable!(&block)
|
|
45
|
+
__set_test_mode(:disable, &block)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def fake!(&block)
|
|
49
|
+
__set_test_mode(:fake, &block)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def inline!(&block)
|
|
53
|
+
__set_test_mode(:inline, &block)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def enabled?
|
|
57
|
+
__test_mode != :disable
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def disabled?
|
|
61
|
+
__test_mode == :disable
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def fake?
|
|
65
|
+
__test_mode == :fake
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def inline?
|
|
69
|
+
__test_mode == :inline
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def server_middleware
|
|
73
|
+
@server_chain ||= Middleware::Chain.new(Sidekiq.default_configuration)
|
|
74
|
+
yield @server_chain if block_given?
|
|
75
|
+
@server_chain
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
class EmptyQueueError < RuntimeError; end
|
|
81
|
+
|
|
82
|
+
module TestingClient
|
|
83
|
+
private def atomic_push(conn, payloads)
|
|
84
|
+
if Sidekiq::Testing.fake?
|
|
85
|
+
payloads.each do |job|
|
|
86
|
+
job = Sidekiq.load_json(Sidekiq.dump_json(job))
|
|
87
|
+
job["enqueued_at"] = ::Process.clock_gettime(::Process::CLOCK_REALTIME, :millisecond) unless job["at"]
|
|
88
|
+
Queues.push(job["queue"], job["class"], job)
|
|
89
|
+
end
|
|
90
|
+
true
|
|
91
|
+
elsif Sidekiq::Testing.inline?
|
|
92
|
+
payloads.each do |job|
|
|
93
|
+
klass = Object.const_get(job["class"])
|
|
94
|
+
job["id"] ||= SecureRandom.hex(12)
|
|
95
|
+
job_hash = Sidekiq.load_json(Sidekiq.dump_json(job))
|
|
96
|
+
klass.process_job(job_hash)
|
|
97
|
+
end
|
|
98
|
+
true
|
|
99
|
+
else
|
|
100
|
+
super
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
Sidekiq::Client.prepend TestingClient
|
|
106
|
+
|
|
107
|
+
module Queues
|
|
108
|
+
##
|
|
109
|
+
# The Queues class is only for testing the fake queue implementation.
|
|
110
|
+
# There are 2 data structures involved in tandem. This is due to the
|
|
111
|
+
# Rspec syntax of change(HardJob.jobs, :size). It keeps a reference
|
|
112
|
+
# to the array. Because the array was derived from a filter of the total
|
|
113
|
+
# jobs enqueued, it appeared as though the array didn't change.
|
|
114
|
+
#
|
|
115
|
+
# To solve this, we'll keep 2 hashes containing the jobs. One with keys based
|
|
116
|
+
# on the queue, and another with keys of the job type, so the array for
|
|
117
|
+
# HardJob.jobs is a straight reference to a real array.
|
|
118
|
+
#
|
|
119
|
+
# Queue-based hash:
|
|
120
|
+
#
|
|
121
|
+
# {
|
|
122
|
+
# "default"=>[
|
|
123
|
+
# {
|
|
124
|
+
# "class"=>"TestTesting::HardJob",
|
|
125
|
+
# "args"=>[1, 2],
|
|
126
|
+
# "retry"=>true,
|
|
127
|
+
# "queue"=>"default",
|
|
128
|
+
# "jid"=>"abc5b065c5c4b27fc1102833",
|
|
129
|
+
# "created_at"=>1447445554.419934
|
|
130
|
+
# }
|
|
131
|
+
# ]
|
|
132
|
+
# }
|
|
133
|
+
#
|
|
134
|
+
# Job-based hash:
|
|
135
|
+
#
|
|
136
|
+
# {
|
|
137
|
+
# "TestTesting::HardJob"=>[
|
|
138
|
+
# {
|
|
139
|
+
# "class"=>"TestTesting::HardJob",
|
|
140
|
+
# "args"=>[1, 2],
|
|
141
|
+
# "retry"=>true,
|
|
142
|
+
# "queue"=>"default",
|
|
143
|
+
# "jid"=>"abc5b065c5c4b27fc1102833",
|
|
144
|
+
# "created_at"=>1447445554.419934
|
|
145
|
+
# }
|
|
146
|
+
# ]
|
|
147
|
+
# }
|
|
148
|
+
#
|
|
149
|
+
# Example:
|
|
150
|
+
#
|
|
151
|
+
# require 'sidekiq/testing'
|
|
152
|
+
#
|
|
153
|
+
# assert_equal 0, Sidekiq::Queues["default"].size
|
|
154
|
+
# HardJob.perform_async(:something)
|
|
155
|
+
# assert_equal 1, Sidekiq::Queues["default"].size
|
|
156
|
+
# assert_equal :something, Sidekiq::Queues["default"].first['args'][0]
|
|
157
|
+
#
|
|
158
|
+
# You can also clear all jobs:
|
|
159
|
+
#
|
|
160
|
+
# assert_equal 0, Sidekiq::Queues["default"].size
|
|
161
|
+
# HardJob.perform_async(:something)
|
|
162
|
+
# Sidekiq::Queues.clear_all
|
|
163
|
+
# assert_equal 0, Sidekiq::Queues["default"].size
|
|
164
|
+
#
|
|
165
|
+
# This can be useful to make sure jobs don't linger between tests:
|
|
166
|
+
#
|
|
167
|
+
# RSpec.configure do |config|
|
|
168
|
+
# config.before(:each) do
|
|
169
|
+
# Sidekiq::Queues.clear_all
|
|
170
|
+
# end
|
|
171
|
+
# end
|
|
172
|
+
#
|
|
173
|
+
class << self
|
|
174
|
+
def [](queue)
|
|
175
|
+
jobs_by_queue[queue]
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def push(queue, klass, job)
|
|
179
|
+
jobs_by_queue[queue] << job
|
|
180
|
+
jobs_by_class[klass] << job
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def jobs_by_queue
|
|
184
|
+
@jobs_by_queue ||= Hash.new { |hash, key| hash[key] = [] }
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def jobs_by_class
|
|
188
|
+
@jobs_by_class ||= Hash.new { |hash, key| hash[key] = [] }
|
|
189
|
+
end
|
|
190
|
+
alias_method :jobs_by_worker, :jobs_by_class
|
|
191
|
+
|
|
192
|
+
def delete_for(jid, queue, klass)
|
|
193
|
+
jobs_by_queue[queue.to_s].delete_if { |job| job["jid"] == jid }
|
|
194
|
+
jobs_by_class[klass].delete_if { |job| job["jid"] == jid }
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
def clear_for(queue, klass)
|
|
198
|
+
jobs_by_queue[queue.to_s].clear
|
|
199
|
+
jobs_by_class[klass].clear
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
def clear_all
|
|
203
|
+
jobs_by_queue.clear
|
|
204
|
+
jobs_by_class.clear
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
module Job
|
|
210
|
+
##
|
|
211
|
+
# The Sidekiq testing infrastructure overrides perform_async
|
|
212
|
+
# so that it does not actually touch the network. Instead it
|
|
213
|
+
# stores the asynchronous jobs in a per-class array so that
|
|
214
|
+
# their presence/absence can be asserted by your tests.
|
|
215
|
+
#
|
|
216
|
+
# This is similar to ActionMailer's :test delivery_method and its
|
|
217
|
+
# ActionMailer::Base.deliveries array.
|
|
218
|
+
#
|
|
219
|
+
# Example:
|
|
220
|
+
#
|
|
221
|
+
# require 'sidekiq/testing'
|
|
222
|
+
#
|
|
223
|
+
# assert_equal 0, HardJob.jobs.size
|
|
224
|
+
# HardJob.perform_async(:something)
|
|
225
|
+
# assert_equal 1, HardJob.jobs.size
|
|
226
|
+
# assert_equal :something, HardJob.jobs[0]['args'][0]
|
|
227
|
+
#
|
|
228
|
+
# You can also clear and drain all job types:
|
|
229
|
+
#
|
|
230
|
+
# Sidekiq::Job.clear_all # or .drain_all
|
|
231
|
+
#
|
|
232
|
+
# This can be useful to make sure jobs don't linger between tests:
|
|
233
|
+
#
|
|
234
|
+
# RSpec.configure do |config|
|
|
235
|
+
# config.before(:each) do
|
|
236
|
+
# Sidekiq::Job.clear_all
|
|
237
|
+
# end
|
|
238
|
+
# end
|
|
239
|
+
#
|
|
240
|
+
# or for acceptance testing, i.e. with cucumber:
|
|
241
|
+
#
|
|
242
|
+
# AfterStep do
|
|
243
|
+
# Sidekiq::Job.drain_all
|
|
244
|
+
# end
|
|
245
|
+
#
|
|
246
|
+
# When I sign up as "foo@example.com"
|
|
247
|
+
# Then I should receive a welcome email to "foo@example.com"
|
|
248
|
+
#
|
|
249
|
+
module ClassMethods
|
|
250
|
+
# Queue for this worker
|
|
251
|
+
def queue
|
|
252
|
+
get_sidekiq_options["queue"]
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
# Jobs queued for this worker
|
|
256
|
+
def jobs
|
|
257
|
+
Queues.jobs_by_class[to_s]
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
# Clear all jobs for this worker
|
|
261
|
+
def clear
|
|
262
|
+
Queues.clear_for(queue, to_s)
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
# Drain and run all jobs for this worker
|
|
266
|
+
def drain
|
|
267
|
+
while jobs.any?
|
|
268
|
+
next_job = jobs.first
|
|
269
|
+
Queues.delete_for(next_job["jid"], next_job["queue"], to_s)
|
|
270
|
+
process_job(next_job)
|
|
271
|
+
end
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
# Pop out a single job and perform it
|
|
275
|
+
def perform_one
|
|
276
|
+
raise(EmptyQueueError, "perform_one called with empty job queue") if jobs.empty?
|
|
277
|
+
next_job = jobs.first
|
|
278
|
+
Queues.delete_for(next_job["jid"], next_job["queue"], to_s)
|
|
279
|
+
process_job(next_job)
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
def process_job(job)
|
|
283
|
+
instance = new
|
|
284
|
+
instance.jid = job["jid"]
|
|
285
|
+
instance.bid = job["bid"] if instance.respond_to?(:bid=)
|
|
286
|
+
Sidekiq::Testing.server_middleware.invoke(instance, job, job["queue"]) do
|
|
287
|
+
execute_job(instance, job["args"])
|
|
288
|
+
end
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
def execute_job(worker, args)
|
|
292
|
+
worker.perform(*args)
|
|
293
|
+
end
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
class << self
|
|
297
|
+
def jobs # :nodoc:
|
|
298
|
+
Queues.jobs_by_queue.values.flatten
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
# Clear all queued jobs
|
|
302
|
+
def clear_all
|
|
303
|
+
Queues.clear_all
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
# Drain (execute) all queued jobs
|
|
307
|
+
def drain_all
|
|
308
|
+
while jobs.any?
|
|
309
|
+
job_classes = jobs.map { |job| job["class"] }.uniq
|
|
310
|
+
|
|
311
|
+
job_classes.each do |job_class|
|
|
312
|
+
Object.const_get(job_class).drain
|
|
313
|
+
end
|
|
314
|
+
end
|
|
315
|
+
end
|
|
316
|
+
end
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
module TestingExtensions
|
|
320
|
+
def jobs_for(klass)
|
|
321
|
+
jobs.select do |job|
|
|
322
|
+
marshalled = job["args"][0]
|
|
323
|
+
marshalled.index(klass.to_s) && YAML.safe_load(marshalled)[0] == klass
|
|
324
|
+
end
|
|
325
|
+
end
|
|
326
|
+
end
|
|
327
|
+
end
|
|
328
|
+
|
|
329
|
+
if defined?(::Rails) && Rails.respond_to?(:env) && !Rails.env.test? && !$TESTING # rubocop:disable Style/GlobalVars
|
|
330
|
+
warn("⛔️ WARNING: Sidekiq testing API enabled, but this is not the test environment. Your jobs will not go to Redis.", uplevel: 1)
|
|
331
|
+
end
|
|
@@ -1,30 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
require "sidekiq/testing"
|
|
4
|
-
|
|
5
|
-
##
|
|
6
|
-
# The Sidekiq inline infrastructure overrides perform_async so that it
|
|
7
|
-
# actually calls perform instead. This allows jobs to be run inline in a
|
|
8
|
-
# testing environment.
|
|
9
|
-
#
|
|
10
|
-
# This is similar to `Resque.inline = true` functionality.
|
|
11
|
-
#
|
|
12
|
-
# Example:
|
|
13
|
-
#
|
|
14
|
-
# require 'sidekiq/testing/inline'
|
|
15
|
-
#
|
|
16
|
-
# $external_variable = 0
|
|
17
|
-
#
|
|
18
|
-
# class ExternalJob
|
|
19
|
-
# include Sidekiq::Job
|
|
20
|
-
#
|
|
21
|
-
# def perform
|
|
22
|
-
# $external_variable = 1
|
|
23
|
-
# end
|
|
24
|
-
# end
|
|
25
|
-
#
|
|
26
|
-
# assert_equal 0, $external_variable
|
|
27
|
-
# ExternalJob.perform_async
|
|
28
|
-
# assert_equal 1, $external_variable
|
|
29
|
-
#
|
|
30
|
-
Sidekiq::Testing.inline!
|
|
1
|
+
Sidekiq.testing!(:inline)
|
|
2
|
+
warn('⛔️ `require "sidekiq/testing/inline"` is deprecated and will be removed in Sidekiq 9.0. See https://sidekiq.org/wiki/Testing#new-api')
|