sidekiq 6.2.2 → 8.1.5

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 (181) hide show
  1. checksums.yaml +4 -4
  2. data/Changes.md +726 -11
  3. data/LICENSE.txt +9 -0
  4. data/README.md +70 -39
  5. data/bin/kiq +17 -0
  6. data/bin/lint-herb +13 -0
  7. data/bin/multi_queue_bench +271 -0
  8. data/bin/sidekiq +4 -9
  9. data/bin/sidekiqload +214 -115
  10. data/bin/sidekiqmon +4 -1
  11. data/bin/webload +69 -0
  12. data/lib/active_job/queue_adapters/sidekiq_adapter.rb +124 -0
  13. data/lib/generators/sidekiq/job_generator.rb +71 -0
  14. data/lib/generators/sidekiq/templates/{worker.rb.erb → job.rb.erb} +3 -3
  15. data/lib/generators/sidekiq/templates/{worker_spec.rb.erb → job_spec.rb.erb} +1 -1
  16. data/lib/generators/sidekiq/templates/{worker_test.rb.erb → job_test.rb.erb} +1 -1
  17. data/lib/sidekiq/api.rb +729 -264
  18. data/lib/sidekiq/capsule.rb +135 -0
  19. data/lib/sidekiq/cli.rb +124 -100
  20. data/lib/sidekiq/client.rb +153 -106
  21. data/lib/sidekiq/component.rb +132 -0
  22. data/lib/sidekiq/config.rb +320 -0
  23. data/lib/sidekiq/deploy.rb +64 -0
  24. data/lib/sidekiq/embedded.rb +64 -0
  25. data/lib/sidekiq/fetch.rb +27 -26
  26. data/lib/sidekiq/iterable_job.rb +56 -0
  27. data/lib/sidekiq/job/interrupt_handler.rb +24 -0
  28. data/lib/sidekiq/job/iterable/active_record_enumerator.rb +53 -0
  29. data/lib/sidekiq/job/iterable/csv_enumerator.rb +47 -0
  30. data/lib/sidekiq/job/iterable/enumerators.rb +135 -0
  31. data/lib/sidekiq/job/iterable.rb +322 -0
  32. data/lib/sidekiq/job.rb +397 -5
  33. data/lib/sidekiq/job_logger.rb +23 -32
  34. data/lib/sidekiq/job_retry.rb +141 -68
  35. data/lib/sidekiq/job_util.rb +113 -0
  36. data/lib/sidekiq/launcher.rb +122 -98
  37. data/lib/sidekiq/loader.rb +57 -0
  38. data/lib/sidekiq/logger.rb +27 -106
  39. data/lib/sidekiq/manager.rb +41 -43
  40. data/lib/sidekiq/metrics/query.rb +184 -0
  41. data/lib/sidekiq/metrics/shared.rb +109 -0
  42. data/lib/sidekiq/metrics/tracking.rb +153 -0
  43. data/lib/sidekiq/middleware/chain.rb +96 -51
  44. data/lib/sidekiq/middleware/current_attributes.rb +120 -0
  45. data/lib/sidekiq/middleware/i18n.rb +8 -4
  46. data/lib/sidekiq/middleware/modules.rb +23 -0
  47. data/lib/sidekiq/monitor.rb +16 -6
  48. data/lib/sidekiq/paginator.rb +37 -10
  49. data/lib/sidekiq/processor.rb +105 -87
  50. data/lib/sidekiq/profiler.rb +73 -0
  51. data/lib/sidekiq/rails.rb +49 -36
  52. data/lib/sidekiq/redis_client_adapter.rb +117 -0
  53. data/lib/sidekiq/redis_connection.rb +55 -86
  54. data/lib/sidekiq/ring_buffer.rb +32 -0
  55. data/lib/sidekiq/scheduled.rb +106 -50
  56. data/lib/sidekiq/systemd.rb +2 -0
  57. data/lib/sidekiq/test_api.rb +331 -0
  58. data/lib/sidekiq/testing/inline.rb +2 -30
  59. data/lib/sidekiq/testing.rb +2 -342
  60. data/lib/sidekiq/transaction_aware_client.rb +59 -0
  61. data/lib/sidekiq/tui/controls.rb +53 -0
  62. data/lib/sidekiq/tui/filtering.rb +53 -0
  63. data/lib/sidekiq/tui/tabs/base_tab.rb +204 -0
  64. data/lib/sidekiq/tui/tabs/busy.rb +118 -0
  65. data/lib/sidekiq/tui/tabs/dead.rb +19 -0
  66. data/lib/sidekiq/tui/tabs/home.rb +144 -0
  67. data/lib/sidekiq/tui/tabs/metrics.rb +131 -0
  68. data/lib/sidekiq/tui/tabs/queues.rb +95 -0
  69. data/lib/sidekiq/tui/tabs/retries.rb +19 -0
  70. data/lib/sidekiq/tui/tabs/scheduled.rb +19 -0
  71. data/lib/sidekiq/tui/tabs/set_tab.rb +96 -0
  72. data/lib/sidekiq/tui/tabs.rb +15 -0
  73. data/lib/sidekiq/tui.rb +382 -0
  74. data/lib/sidekiq/version.rb +6 -1
  75. data/lib/sidekiq/web/action.rb +149 -64
  76. data/lib/sidekiq/web/application.rb +376 -268
  77. data/lib/sidekiq/web/config.rb +117 -0
  78. data/lib/sidekiq/web/helpers.rb +213 -87
  79. data/lib/sidekiq/web/router.rb +61 -74
  80. data/lib/sidekiq/web.rb +71 -100
  81. data/lib/sidekiq/worker_compatibility_alias.rb +13 -0
  82. data/lib/sidekiq.rb +95 -196
  83. data/sidekiq.gemspec +14 -11
  84. data/web/assets/images/logo.png +0 -0
  85. data/web/assets/images/status.png +0 -0
  86. data/web/assets/javascripts/application.js +171 -57
  87. data/web/assets/javascripts/base-charts.js +120 -0
  88. data/web/assets/javascripts/chart.min.js +13 -0
  89. data/web/assets/javascripts/chartjs-adapter-date-fns.min.js +7 -0
  90. data/web/assets/javascripts/chartjs-plugin-annotation.min.js +7 -0
  91. data/web/assets/javascripts/dashboard-charts.js +194 -0
  92. data/web/assets/javascripts/dashboard.js +41 -274
  93. data/web/assets/javascripts/metrics.js +280 -0
  94. data/web/assets/stylesheets/style.css +776 -0
  95. data/web/locales/ar.yml +72 -70
  96. data/web/locales/cs.yml +64 -62
  97. data/web/locales/da.yml +62 -53
  98. data/web/locales/de.yml +67 -65
  99. data/web/locales/el.yml +45 -24
  100. data/web/locales/en.yml +93 -69
  101. data/web/locales/es.yml +91 -68
  102. data/web/locales/fa.yml +67 -65
  103. data/web/locales/fr.yml +82 -67
  104. data/web/locales/gd.yml +110 -0
  105. data/web/locales/he.yml +67 -64
  106. data/web/locales/hi.yml +61 -59
  107. data/web/locales/it.yml +94 -54
  108. data/web/locales/ja.yml +74 -68
  109. data/web/locales/ko.yml +54 -52
  110. data/web/locales/lt.yml +68 -66
  111. data/web/locales/nb.yml +63 -61
  112. data/web/locales/nl.yml +54 -52
  113. data/web/locales/pl.yml +47 -45
  114. data/web/locales/{pt-br.yml → pt-BR.yml} +85 -56
  115. data/web/locales/pt.yml +53 -51
  116. data/web/locales/ru.yml +69 -66
  117. data/web/locales/sv.yml +55 -53
  118. data/web/locales/ta.yml +62 -60
  119. data/web/locales/tr.yml +102 -0
  120. data/web/locales/uk.yml +87 -61
  121. data/web/locales/ur.yml +66 -64
  122. data/web/locales/vi.yml +69 -67
  123. data/web/locales/zh-CN.yml +107 -0
  124. data/web/locales/{zh-tw.yml → zh-TW.yml} +44 -9
  125. data/web/views/_footer.html.erb +32 -0
  126. data/web/views/_job_info.html.erb +115 -0
  127. data/web/views/_metrics_period_select.html.erb +15 -0
  128. data/web/views/_nav.html.erb +45 -0
  129. data/web/views/_paging.html.erb +26 -0
  130. data/web/views/_poll_link.html.erb +4 -0
  131. data/web/views/_summary.html.erb +40 -0
  132. data/web/views/busy.html.erb +151 -0
  133. data/web/views/dashboard.html.erb +104 -0
  134. data/web/views/dead.html.erb +38 -0
  135. data/web/views/filtering.html.erb +6 -0
  136. data/web/views/layout.html.erb +26 -0
  137. data/web/views/metrics.html.erb +85 -0
  138. data/web/views/metrics_for_job.html.erb +58 -0
  139. data/web/views/morgue.html.erb +69 -0
  140. data/web/views/profiles.html.erb +43 -0
  141. data/web/views/queue.html.erb +57 -0
  142. data/web/views/queues.html.erb +46 -0
  143. data/web/views/retries.html.erb +77 -0
  144. data/web/views/retry.html.erb +39 -0
  145. data/web/views/scheduled.html.erb +64 -0
  146. data/web/views/{scheduled_job_info.erb → scheduled_job_info.html.erb} +3 -3
  147. metadata +130 -61
  148. data/LICENSE +0 -9
  149. data/lib/generators/sidekiq/worker_generator.rb +0 -57
  150. data/lib/sidekiq/delay.rb +0 -41
  151. data/lib/sidekiq/exception_handler.rb +0 -27
  152. data/lib/sidekiq/extensions/action_mailer.rb +0 -48
  153. data/lib/sidekiq/extensions/active_record.rb +0 -43
  154. data/lib/sidekiq/extensions/class_methods.rb +0 -43
  155. data/lib/sidekiq/extensions/generic_proxy.rb +0 -33
  156. data/lib/sidekiq/util.rb +0 -95
  157. data/lib/sidekiq/web/csrf_protection.rb +0 -180
  158. data/lib/sidekiq/worker.rb +0 -244
  159. data/web/assets/stylesheets/application-dark.css +0 -147
  160. data/web/assets/stylesheets/application-rtl.css +0 -246
  161. data/web/assets/stylesheets/application.css +0 -1053
  162. data/web/assets/stylesheets/bootstrap-rtl.min.css +0 -9
  163. data/web/assets/stylesheets/bootstrap.css +0 -5
  164. data/web/locales/zh-cn.yml +0 -68
  165. data/web/views/_footer.erb +0 -20
  166. data/web/views/_job_info.erb +0 -89
  167. data/web/views/_nav.erb +0 -52
  168. data/web/views/_paging.erb +0 -23
  169. data/web/views/_poll_link.erb +0 -7
  170. data/web/views/_status.erb +0 -4
  171. data/web/views/_summary.erb +0 -40
  172. data/web/views/busy.erb +0 -132
  173. data/web/views/dashboard.erb +0 -83
  174. data/web/views/dead.erb +0 -34
  175. data/web/views/layout.erb +0 -42
  176. data/web/views/morgue.erb +0 -78
  177. data/web/views/queue.erb +0 -55
  178. data/web/views/queues.erb +0 -38
  179. data/web/views/retries.erb +0 -83
  180. data/web/views/retry.erb +0 -34
  181. data/web/views/scheduled.erb +0 -57
@@ -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
- # frozen_string_literal: true
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 workers 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 ExternalWorker
19
- # include Sidekiq::Worker
20
- #
21
- # def perform
22
- # $external_variable = 1
23
- # end
24
- # end
25
- #
26
- # assert_equal 0, $external_variable
27
- # ExternalWorker.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')
@@ -1,342 +1,2 @@
1
- # frozen_string_literal: true
2
-
3
- require "securerandom"
4
- require "sidekiq"
5
-
6
- module Sidekiq
7
- class Testing
8
- class << self
9
- attr_accessor :__test_mode
10
-
11
- def __set_test_mode(mode)
12
- if block_given?
13
- current_mode = __test_mode
14
- begin
15
- self.__test_mode = mode
16
- yield
17
- ensure
18
- self.__test_mode = current_mode
19
- end
20
- else
21
- self.__test_mode = mode
22
- end
23
- end
24
-
25
- def disable!(&block)
26
- __set_test_mode(:disable, &block)
27
- end
28
-
29
- def fake!(&block)
30
- __set_test_mode(:fake, &block)
31
- end
32
-
33
- def inline!(&block)
34
- __set_test_mode(:inline, &block)
35
- end
36
-
37
- def enabled?
38
- __test_mode != :disable
39
- end
40
-
41
- def disabled?
42
- __test_mode == :disable
43
- end
44
-
45
- def fake?
46
- __test_mode == :fake
47
- end
48
-
49
- def inline?
50
- __test_mode == :inline
51
- end
52
-
53
- def server_middleware
54
- @server_chain ||= Middleware::Chain.new
55
- yield @server_chain if block_given?
56
- @server_chain
57
- end
58
-
59
- def constantize(str)
60
- names = str.split("::")
61
- names.shift if names.empty? || names.first.empty?
62
-
63
- names.inject(Object) do |constant, name|
64
- constant.const_defined?(name) ? constant.const_get(name) : constant.const_missing(name)
65
- end
66
- end
67
- end
68
- end
69
-
70
- # Default to fake testing to keep old behavior
71
- Sidekiq::Testing.fake!
72
-
73
- class EmptyQueueError < RuntimeError; end
74
-
75
- module TestingClient
76
- def raw_push(payloads)
77
- if Sidekiq::Testing.fake?
78
- payloads.each do |job|
79
- job = Sidekiq.load_json(Sidekiq.dump_json(job))
80
- job["enqueued_at"] = Time.now.to_f unless job["at"]
81
- Queues.push(job["queue"], job["class"], job)
82
- end
83
- true
84
- elsif Sidekiq::Testing.inline?
85
- payloads.each do |job|
86
- klass = Sidekiq::Testing.constantize(job["class"])
87
- job["id"] ||= SecureRandom.hex(12)
88
- job_hash = Sidekiq.load_json(Sidekiq.dump_json(job))
89
- klass.process_job(job_hash)
90
- end
91
- true
92
- else
93
- super
94
- end
95
- end
96
- end
97
-
98
- Sidekiq::Client.prepend TestingClient
99
-
100
- module Queues
101
- ##
102
- # The Queues class is only for testing the fake queue implementation.
103
- # There are 2 data structures involved in tandem. This is due to the
104
- # Rspec syntax of change(QueueWorker.jobs, :size). It keeps a reference
105
- # to the array. Because the array was dervied from a filter of the total
106
- # jobs enqueued, it appeared as though the array didn't change.
107
- #
108
- # To solve this, we'll keep 2 hashes containing the jobs. One with keys based
109
- # on the queue, and another with keys of the worker names, so the array for
110
- # QueueWorker.jobs is a straight reference to a real array.
111
- #
112
- # Queue-based hash:
113
- #
114
- # {
115
- # "default"=>[
116
- # {
117
- # "class"=>"TestTesting::QueueWorker",
118
- # "args"=>[1, 2],
119
- # "retry"=>true,
120
- # "queue"=>"default",
121
- # "jid"=>"abc5b065c5c4b27fc1102833",
122
- # "created_at"=>1447445554.419934
123
- # }
124
- # ]
125
- # }
126
- #
127
- # Worker-based hash:
128
- #
129
- # {
130
- # "TestTesting::QueueWorker"=>[
131
- # {
132
- # "class"=>"TestTesting::QueueWorker",
133
- # "args"=>[1, 2],
134
- # "retry"=>true,
135
- # "queue"=>"default",
136
- # "jid"=>"abc5b065c5c4b27fc1102833",
137
- # "created_at"=>1447445554.419934
138
- # }
139
- # ]
140
- # }
141
- #
142
- # Example:
143
- #
144
- # require 'sidekiq/testing'
145
- #
146
- # assert_equal 0, Sidekiq::Queues["default"].size
147
- # HardWorker.perform_async(:something)
148
- # assert_equal 1, Sidekiq::Queues["default"].size
149
- # assert_equal :something, Sidekiq::Queues["default"].first['args'][0]
150
- #
151
- # You can also clear all workers' jobs:
152
- #
153
- # assert_equal 0, Sidekiq::Queues["default"].size
154
- # HardWorker.perform_async(:something)
155
- # Sidekiq::Queues.clear_all
156
- # assert_equal 0, Sidekiq::Queues["default"].size
157
- #
158
- # This can be useful to make sure jobs don't linger between tests:
159
- #
160
- # RSpec.configure do |config|
161
- # config.before(:each) do
162
- # Sidekiq::Queues.clear_all
163
- # end
164
- # end
165
- #
166
- class << self
167
- def [](queue)
168
- jobs_by_queue[queue]
169
- end
170
-
171
- def push(queue, klass, job)
172
- jobs_by_queue[queue] << job
173
- jobs_by_worker[klass] << job
174
- end
175
-
176
- def jobs_by_queue
177
- @jobs_by_queue ||= Hash.new { |hash, key| hash[key] = [] }
178
- end
179
-
180
- def jobs_by_worker
181
- @jobs_by_worker ||= Hash.new { |hash, key| hash[key] = [] }
182
- end
183
-
184
- def delete_for(jid, queue, klass)
185
- jobs_by_queue[queue.to_s].delete_if { |job| job["jid"] == jid }
186
- jobs_by_worker[klass].delete_if { |job| job["jid"] == jid }
187
- end
188
-
189
- def clear_for(queue, klass)
190
- jobs_by_queue[queue].clear
191
- jobs_by_worker[klass].clear
192
- end
193
-
194
- def clear_all
195
- jobs_by_queue.clear
196
- jobs_by_worker.clear
197
- end
198
- end
199
- end
200
-
201
- module Worker
202
- ##
203
- # The Sidekiq testing infrastructure overrides perform_async
204
- # so that it does not actually touch the network. Instead it
205
- # stores the asynchronous jobs in a per-class array so that
206
- # their presence/absence can be asserted by your tests.
207
- #
208
- # This is similar to ActionMailer's :test delivery_method and its
209
- # ActionMailer::Base.deliveries array.
210
- #
211
- # Example:
212
- #
213
- # require 'sidekiq/testing'
214
- #
215
- # assert_equal 0, HardWorker.jobs.size
216
- # HardWorker.perform_async(:something)
217
- # assert_equal 1, HardWorker.jobs.size
218
- # assert_equal :something, HardWorker.jobs[0]['args'][0]
219
- #
220
- # assert_equal 0, Sidekiq::Extensions::DelayedMailer.jobs.size
221
- # MyMailer.delay.send_welcome_email('foo@example.com')
222
- # assert_equal 1, Sidekiq::Extensions::DelayedMailer.jobs.size
223
- #
224
- # You can also clear and drain all workers' jobs:
225
- #
226
- # assert_equal 0, Sidekiq::Extensions::DelayedMailer.jobs.size
227
- # assert_equal 0, Sidekiq::Extensions::DelayedModel.jobs.size
228
- #
229
- # MyMailer.delay.send_welcome_email('foo@example.com')
230
- # MyModel.delay.do_something_hard
231
- #
232
- # assert_equal 1, Sidekiq::Extensions::DelayedMailer.jobs.size
233
- # assert_equal 1, Sidekiq::Extensions::DelayedModel.jobs.size
234
- #
235
- # Sidekiq::Worker.clear_all # or .drain_all
236
- #
237
- # assert_equal 0, Sidekiq::Extensions::DelayedMailer.jobs.size
238
- # assert_equal 0, Sidekiq::Extensions::DelayedModel.jobs.size
239
- #
240
- # This can be useful to make sure jobs don't linger between tests:
241
- #
242
- # RSpec.configure do |config|
243
- # config.before(:each) do
244
- # Sidekiq::Worker.clear_all
245
- # end
246
- # end
247
- #
248
- # or for acceptance testing, i.e. with cucumber:
249
- #
250
- # AfterStep do
251
- # Sidekiq::Worker.drain_all
252
- # end
253
- #
254
- # When I sign up as "foo@example.com"
255
- # Then I should receive a welcome email to "foo@example.com"
256
- #
257
- module ClassMethods
258
- # Queue for this worker
259
- def queue
260
- get_sidekiq_options["queue"]
261
- end
262
-
263
- # Jobs queued for this worker
264
- def jobs
265
- Queues.jobs_by_worker[to_s]
266
- end
267
-
268
- # Clear all jobs for this worker
269
- def clear
270
- Queues.clear_for(queue, to_s)
271
- end
272
-
273
- # Drain and run all jobs for this worker
274
- def drain
275
- while jobs.any?
276
- next_job = jobs.first
277
- Queues.delete_for(next_job["jid"], next_job["queue"], to_s)
278
- process_job(next_job)
279
- end
280
- end
281
-
282
- # Pop out a single job and perform it
283
- def perform_one
284
- raise(EmptyQueueError, "perform_one called with empty job queue") if jobs.empty?
285
- next_job = jobs.first
286
- Queues.delete_for(next_job["jid"], queue, to_s)
287
- process_job(next_job)
288
- end
289
-
290
- def process_job(job)
291
- worker = new
292
- worker.jid = job["jid"]
293
- worker.bid = job["bid"] if worker.respond_to?(:bid=)
294
- Sidekiq::Testing.server_middleware.invoke(worker, job, job["queue"]) do
295
- execute_job(worker, job["args"])
296
- end
297
- end
298
-
299
- def execute_job(worker, args)
300
- worker.perform(*args)
301
- end
302
- end
303
-
304
- class << self
305
- def jobs # :nodoc:
306
- Queues.jobs_by_queue.values.flatten
307
- end
308
-
309
- # Clear all queued jobs across all workers
310
- def clear_all
311
- Queues.clear_all
312
- end
313
-
314
- # Drain all queued jobs across all workers
315
- def drain_all
316
- while jobs.any?
317
- worker_classes = jobs.map { |job| job["class"] }.uniq
318
-
319
- worker_classes.each do |worker_class|
320
- Sidekiq::Testing.constantize(worker_class).drain
321
- end
322
- end
323
- end
324
- end
325
- end
326
-
327
- module TestingExtensions
328
- def jobs_for(klass)
329
- jobs.select do |job|
330
- marshalled = job["args"][0]
331
- marshalled.index(klass.to_s) && YAML.load(marshalled)[0] == klass
332
- end
333
- end
334
- end
335
-
336
- Sidekiq::Extensions::DelayedMailer.extend(TestingExtensions) if defined?(Sidekiq::Extensions::DelayedMailer)
337
- Sidekiq::Extensions::DelayedModel.extend(TestingExtensions) if defined?(Sidekiq::Extensions::DelayedModel)
338
- end
339
-
340
- if defined?(::Rails) && Rails.respond_to?(:env) && !Rails.env.test? && !$TESTING
341
- warn("⛔️ WARNING: Sidekiq testing API enabled, but this is not the test environment. Your jobs will not go to Redis.", uplevel: 1)
342
- end
1
+ Sidekiq.testing!(:fake)
2
+ warn('⛔️ `require "sidekiq/testing"` is deprecated and will be removed in Sidekiq 9.0. See https://sidekiq.org/wiki/Testing#new-api')