delayed 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +20 -0
  3. data/README.md +560 -0
  4. data/Rakefile +35 -0
  5. data/lib/delayed.rb +72 -0
  6. data/lib/delayed/active_job_adapter.rb +65 -0
  7. data/lib/delayed/backend/base.rb +166 -0
  8. data/lib/delayed/backend/job_preparer.rb +43 -0
  9. data/lib/delayed/exceptions.rb +14 -0
  10. data/lib/delayed/job.rb +250 -0
  11. data/lib/delayed/lifecycle.rb +85 -0
  12. data/lib/delayed/message_sending.rb +65 -0
  13. data/lib/delayed/monitor.rb +134 -0
  14. data/lib/delayed/performable_mailer.rb +22 -0
  15. data/lib/delayed/performable_method.rb +47 -0
  16. data/lib/delayed/plugin.rb +15 -0
  17. data/lib/delayed/plugins/connection.rb +13 -0
  18. data/lib/delayed/plugins/instrumentation.rb +39 -0
  19. data/lib/delayed/priority.rb +164 -0
  20. data/lib/delayed/psych_ext.rb +135 -0
  21. data/lib/delayed/railtie.rb +7 -0
  22. data/lib/delayed/runnable.rb +46 -0
  23. data/lib/delayed/serialization/active_record.rb +18 -0
  24. data/lib/delayed/syck_ext.rb +42 -0
  25. data/lib/delayed/tasks.rb +40 -0
  26. data/lib/delayed/worker.rb +233 -0
  27. data/lib/delayed/yaml_ext.rb +10 -0
  28. data/lib/delayed_job.rb +1 -0
  29. data/lib/delayed_job_active_record.rb +1 -0
  30. data/lib/generators/delayed/generator.rb +7 -0
  31. data/lib/generators/delayed/migration_generator.rb +28 -0
  32. data/lib/generators/delayed/next_migration_version.rb +14 -0
  33. data/lib/generators/delayed/templates/migration.rb +22 -0
  34. data/spec/autoloaded/clazz.rb +6 -0
  35. data/spec/autoloaded/instance_clazz.rb +5 -0
  36. data/spec/autoloaded/instance_struct.rb +6 -0
  37. data/spec/autoloaded/struct.rb +7 -0
  38. data/spec/database.yml +25 -0
  39. data/spec/delayed/active_job_adapter_spec.rb +267 -0
  40. data/spec/delayed/job_spec.rb +953 -0
  41. data/spec/delayed/monitor_spec.rb +276 -0
  42. data/spec/delayed/plugins/instrumentation_spec.rb +49 -0
  43. data/spec/delayed/priority_spec.rb +154 -0
  44. data/spec/delayed/serialization/active_record_spec.rb +15 -0
  45. data/spec/delayed/tasks_spec.rb +116 -0
  46. data/spec/helper.rb +196 -0
  47. data/spec/lifecycle_spec.rb +77 -0
  48. data/spec/message_sending_spec.rb +149 -0
  49. data/spec/performable_mailer_spec.rb +68 -0
  50. data/spec/performable_method_spec.rb +123 -0
  51. data/spec/psych_ext_spec.rb +94 -0
  52. data/spec/sample_jobs.rb +117 -0
  53. data/spec/worker_spec.rb +235 -0
  54. data/spec/yaml_ext_spec.rb +48 -0
  55. metadata +326 -0
@@ -0,0 +1,276 @@
1
+ require 'helper'
2
+
3
+ RSpec.describe Delayed::Monitor do
4
+ before do
5
+ described_class.sleep_delay = 0
6
+ end
7
+
8
+ let(:default_payload) do
9
+ {
10
+ table: 'delayed_jobs',
11
+ database: current_database,
12
+ database_adapter: current_adapter,
13
+ queue: 'default',
14
+ }
15
+ end
16
+
17
+ it 'emits empty metrics for all default priorities' do
18
+ expect { subject.run! }
19
+ .to emit_notification("delayed.monitor.run").with_payload(default_payload.except(:queue))
20
+ .and emit_notification("delayed.job.count").with_payload(default_payload.merge(priority: 'interactive')).with_value(0)
21
+ .and emit_notification("delayed.job.count").with_payload(default_payload.merge(priority: 'user_visible')).with_value(0)
22
+ .and emit_notification("delayed.job.count").with_payload(default_payload.merge(priority: 'eventual')).with_value(0)
23
+ .and emit_notification("delayed.job.count").with_payload(default_payload.merge(priority: 'reporting')).with_value(0)
24
+ .and emit_notification("delayed.job.future_count").with_payload(default_payload.merge(priority: 'interactive')).with_value(0)
25
+ .and emit_notification("delayed.job.future_count").with_payload(default_payload.merge(priority: 'user_visible')).with_value(0)
26
+ .and emit_notification("delayed.job.future_count").with_payload(default_payload.merge(priority: 'eventual')).with_value(0)
27
+ .and emit_notification("delayed.job.future_count").with_payload(default_payload.merge(priority: 'reporting')).with_value(0)
28
+ .and emit_notification("delayed.job.locked_count").with_payload(default_payload.merge(priority: 'interactive')).with_value(0)
29
+ .and emit_notification("delayed.job.locked_count").with_payload(default_payload.merge(priority: 'user_visible')).with_value(0)
30
+ .and emit_notification("delayed.job.locked_count").with_payload(default_payload.merge(priority: 'eventual')).with_value(0)
31
+ .and emit_notification("delayed.job.locked_count").with_payload(default_payload.merge(priority: 'reporting')).with_value(0)
32
+ .and emit_notification("delayed.job.erroring_count").with_payload(default_payload.merge(priority: 'interactive')).with_value(0)
33
+ .and emit_notification("delayed.job.erroring_count").with_payload(default_payload.merge(priority: 'user_visible')).with_value(0)
34
+ .and emit_notification("delayed.job.erroring_count").with_payload(default_payload.merge(priority: 'eventual')).with_value(0)
35
+ .and emit_notification("delayed.job.erroring_count").with_payload(default_payload.merge(priority: 'reporting')).with_value(0)
36
+ .and emit_notification("delayed.job.failed_count").with_payload(default_payload.merge(priority: 'interactive')).with_value(0)
37
+ .and emit_notification("delayed.job.failed_count").with_payload(default_payload.merge(priority: 'user_visible')).with_value(0)
38
+ .and emit_notification("delayed.job.failed_count").with_payload(default_payload.merge(priority: 'eventual')).with_value(0)
39
+ .and emit_notification("delayed.job.failed_count").with_payload(default_payload.merge(priority: 'reporting')).with_value(0)
40
+ .and emit_notification("delayed.job.working_count").with_payload(default_payload.merge(priority: 'interactive')).with_value(0)
41
+ .and emit_notification("delayed.job.working_count").with_payload(default_payload.merge(priority: 'user_visible')).with_value(0)
42
+ .and emit_notification("delayed.job.working_count").with_payload(default_payload.merge(priority: 'eventual')).with_value(0)
43
+ .and emit_notification("delayed.job.working_count").with_payload(default_payload.merge(priority: 'reporting')).with_value(0)
44
+ .and emit_notification("delayed.job.workable_count").with_payload(default_payload.merge(priority: 'interactive')).with_value(0)
45
+ .and emit_notification("delayed.job.workable_count").with_payload(default_payload.merge(priority: 'user_visible')).with_value(0)
46
+ .and emit_notification("delayed.job.workable_count").with_payload(default_payload.merge(priority: 'eventual')).with_value(0)
47
+ .and emit_notification("delayed.job.workable_count").with_payload(default_payload.merge(priority: 'reporting')).with_value(0)
48
+ .and emit_notification("delayed.job.max_age").with_payload(default_payload.merge(priority: 'interactive')).with_value(0)
49
+ .and emit_notification("delayed.job.max_age").with_payload(default_payload.merge(priority: 'user_visible')).with_value(0)
50
+ .and emit_notification("delayed.job.max_age").with_payload(default_payload.merge(priority: 'eventual')).with_value(0)
51
+ .and emit_notification("delayed.job.max_age").with_payload(default_payload.merge(priority: 'reporting')).with_value(0)
52
+ .and emit_notification("delayed.job.max_lock_age").with_payload(default_payload.merge(priority: 'interactive')).with_value(0)
53
+ .and emit_notification("delayed.job.max_lock_age").with_payload(default_payload.merge(priority: 'user_visible')).with_value(0)
54
+ .and emit_notification("delayed.job.max_lock_age").with_payload(default_payload.merge(priority: 'eventual')).with_value(0)
55
+ .and emit_notification("delayed.job.max_lock_age").with_payload(default_payload.merge(priority: 'reporting')).with_value(0)
56
+ .and emit_notification("delayed.job.alert_age_percent").with_payload(default_payload.merge(priority: 'interactive')).with_value(0)
57
+ .and emit_notification("delayed.job.alert_age_percent").with_payload(default_payload.merge(priority: 'user_visible')).with_value(0)
58
+ .and emit_notification("delayed.job.alert_age_percent").with_payload(default_payload.merge(priority: 'eventual')).with_value(0)
59
+ .and emit_notification("delayed.job.alert_age_percent").with_payload(default_payload.merge(priority: 'reporting')).with_value(0)
60
+ end
61
+
62
+ context 'when named priorities are customized' do
63
+ around do |example|
64
+ Delayed::Priority.names = { high: 0, low: 7 }
65
+ example.run
66
+ ensure
67
+ Delayed::Priority.names = nil
68
+ end
69
+
70
+ it 'emits empty metrics for all custom priorities' do
71
+ expect { subject.run! }
72
+ .to emit_notification("delayed.monitor.run").with_payload(default_payload.except(:queue))
73
+ .and emit_notification("delayed.job.count").with_payload(default_payload.merge(priority: 'high')).with_value(0)
74
+ .and emit_notification("delayed.job.count").with_payload(default_payload.merge(priority: 'low')).with_value(0)
75
+ .and emit_notification("delayed.job.future_count").with_payload(default_payload.merge(priority: 'high')).with_value(0)
76
+ .and emit_notification("delayed.job.future_count").with_payload(default_payload.merge(priority: 'low')).with_value(0)
77
+ .and emit_notification("delayed.job.locked_count").with_payload(default_payload.merge(priority: 'high')).with_value(0)
78
+ .and emit_notification("delayed.job.locked_count").with_payload(default_payload.merge(priority: 'low')).with_value(0)
79
+ .and emit_notification("delayed.job.erroring_count").with_payload(default_payload.merge(priority: 'high')).with_value(0)
80
+ .and emit_notification("delayed.job.erroring_count").with_payload(default_payload.merge(priority: 'low')).with_value(0)
81
+ .and emit_notification("delayed.job.failed_count").with_payload(default_payload.merge(priority: 'high')).with_value(0)
82
+ .and emit_notification("delayed.job.failed_count").with_payload(default_payload.merge(priority: 'low')).with_value(0)
83
+ .and emit_notification("delayed.job.working_count").with_payload(default_payload.merge(priority: 'high')).with_value(0)
84
+ .and emit_notification("delayed.job.working_count").with_payload(default_payload.merge(priority: 'low')).with_value(0)
85
+ .and emit_notification("delayed.job.workable_count").with_payload(default_payload.merge(priority: 'high')).with_value(0)
86
+ .and emit_notification("delayed.job.workable_count").with_payload(default_payload.merge(priority: 'low')).with_value(0)
87
+ .and emit_notification("delayed.job.max_age").with_payload(default_payload.merge(priority: 'high')).with_value(0)
88
+ .and emit_notification("delayed.job.max_age").with_payload(default_payload.merge(priority: 'low')).with_value(0)
89
+ .and emit_notification("delayed.job.max_lock_age").with_payload(default_payload.merge(priority: 'high')).with_value(0)
90
+ .and emit_notification("delayed.job.max_lock_age").with_payload(default_payload.merge(priority: 'low')).with_value(0)
91
+ .and emit_notification("delayed.job.alert_age_percent").with_payload(default_payload.merge(priority: 'high')).with_value(0)
92
+ .and emit_notification("delayed.job.alert_age_percent").with_payload(default_payload.merge(priority: 'low')).with_value(0)
93
+ end
94
+ end
95
+
96
+ context 'when there are jobs in the queue' do
97
+ let(:now) { Time.now.change(nsec: 0) } # rubocop:disable Rails/TimeZone
98
+ let(:job_attributes) do
99
+ {
100
+ run_at: now,
101
+ queue: 'default',
102
+ handler: "--- !ruby/object:SimpleJob\n",
103
+ attempts: 0,
104
+ }
105
+ end
106
+ let(:failed_attributes) { { run_at: now - 1.week, last_error: '123', failed_at: now - 1.day, attempts: 4, locked_at: now - 1.day } }
107
+ let(:p0_attributes) { job_attributes.merge(priority: 1) }
108
+ let(:p10_attributes) { job_attributes.merge(priority: 13) }
109
+ let(:p20_attributes) { job_attributes.merge(priority: 23) }
110
+ let(:p30_attributes) { job_attributes.merge(priority: 999) }
111
+ let(:p0_payload) { default_payload.merge(priority: 'interactive') }
112
+ let(:p10_payload) { default_payload.merge(priority: 'user_visible') }
113
+ let(:p20_payload) { default_payload.merge(priority: 'eventual') }
114
+ let(:p30_payload) { default_payload.merge(priority: 'reporting') }
115
+ let!(:p0_workable_job) { Delayed::Job.create! p0_attributes.merge(run_at: now - 30.seconds) }
116
+ let!(:p0_failed_job) { Delayed::Job.create! p0_attributes.merge(failed_attributes) }
117
+ let!(:p0_future_job) { Delayed::Job.create! p0_attributes.merge(run_at: now + 1.hour) }
118
+ let!(:p0_working_job) { Delayed::Job.create! p0_attributes.merge(locked_at: now - 3.minutes) }
119
+ let!(:p10_workable_job) { Delayed::Job.create! p10_attributes.merge(run_at: now - 2.minutes) }
120
+ let!(:p10_failed_job) { Delayed::Job.create! p10_attributes.merge(failed_attributes) }
121
+ let!(:p10_future_job) { Delayed::Job.create! p10_attributes.merge(run_at: now + 1.hour) }
122
+ let!(:p10_working_job) { Delayed::Job.create! p10_attributes.merge(locked_at: now - 7.minutes) }
123
+ let!(:p20_workable_job) { Delayed::Job.create! p20_attributes.merge(run_at: now - 1.hour) }
124
+ let!(:p20_failed_job) { Delayed::Job.create! p20_attributes.merge(failed_attributes) }
125
+ let!(:p20_future_job) { Delayed::Job.create! p20_attributes.merge(run_at: now + 1.hour) }
126
+ let!(:p20_working_job) { Delayed::Job.create! p20_attributes.merge(locked_at: now - 9.minutes) }
127
+ let!(:p30_workable_job) { Delayed::Job.create! p30_attributes.merge(run_at: now - 6.hours) }
128
+ let!(:p30_failed_job) { Delayed::Job.create! p30_attributes.merge(failed_attributes) }
129
+ let!(:p30_future_job) { Delayed::Job.create! p30_attributes.merge(run_at: now + 1.hour) }
130
+ let!(:p30_working_job) { Delayed::Job.create! p30_attributes.merge(locked_at: now - 11.minutes) }
131
+ let!(:p30_workable_job_in_other_queue) { Delayed::Job.create! p30_attributes.merge(run_at: now - 4.hours, queue: 'banana') }
132
+
133
+ around do |example|
134
+ Timecop.freeze(now) { example.run }
135
+ end
136
+
137
+ it 'emits the expected results for each metric' do
138
+ expect { subject.run! }
139
+ .to emit_notification("delayed.monitor.run").with_payload(default_payload.except(:queue))
140
+ .and emit_notification("delayed.job.count").with_payload(p0_payload).with_value(4)
141
+ .and emit_notification("delayed.job.future_count").with_payload(p0_payload).with_value(1)
142
+ .and emit_notification("delayed.job.locked_count").with_payload(p0_payload).with_value(2)
143
+ .and emit_notification("delayed.job.erroring_count").with_payload(p0_payload).with_value(1)
144
+ .and emit_notification("delayed.job.failed_count").with_payload(p0_payload).with_value(1)
145
+ .and emit_notification("delayed.job.working_count").with_payload(p0_payload).with_value(1)
146
+ .and emit_notification("delayed.job.workable_count").with_payload(p0_payload).with_value(1)
147
+ .and emit_notification("delayed.job.max_age").with_payload(p0_payload).with_value(30.seconds)
148
+ .and emit_notification("delayed.job.max_lock_age").with_payload(p0_payload).with_value(3.minutes)
149
+ .and emit_notification("delayed.job.alert_age_percent").with_payload(p0_payload).with_value(30.0.seconds / 1.minute * 100)
150
+ .and emit_notification("delayed.job.count").with_payload(p10_payload).with_value(4)
151
+ .and emit_notification("delayed.job.future_count").with_payload(p10_payload).with_value(1)
152
+ .and emit_notification("delayed.job.locked_count").with_payload(p10_payload).with_value(2)
153
+ .and emit_notification("delayed.job.erroring_count").with_payload(p10_payload).with_value(1)
154
+ .and emit_notification("delayed.job.failed_count").with_payload(p10_payload).with_value(1)
155
+ .and emit_notification("delayed.job.working_count").with_payload(p10_payload).with_value(1)
156
+ .and emit_notification("delayed.job.workable_count").with_payload(p10_payload).with_value(1)
157
+ .and emit_notification("delayed.job.max_age").with_payload(p10_payload).with_value(2.minutes)
158
+ .and emit_notification("delayed.job.max_lock_age").with_payload(p10_payload).with_value(7.minutes)
159
+ .and emit_notification("delayed.job.alert_age_percent").with_payload(p10_payload).with_value(2.0.minutes / 3.minutes * 100)
160
+ .and emit_notification("delayed.job.count").with_payload(p20_payload).with_value(4)
161
+ .and emit_notification("delayed.job.future_count").with_payload(p20_payload).with_value(1)
162
+ .and emit_notification("delayed.job.locked_count").with_payload(p20_payload).with_value(2)
163
+ .and emit_notification("delayed.job.erroring_count").with_payload(p20_payload).with_value(1)
164
+ .and emit_notification("delayed.job.failed_count").with_payload(p20_payload).with_value(1)
165
+ .and emit_notification("delayed.job.working_count").with_payload(p20_payload).with_value(1)
166
+ .and emit_notification("delayed.job.workable_count").with_payload(p20_payload).with_value(1)
167
+ .and emit_notification("delayed.job.max_age").with_payload(p20_payload).with_value(1.hour)
168
+ .and emit_notification("delayed.job.max_lock_age").with_payload(p20_payload).with_value(9.minutes)
169
+ .and emit_notification("delayed.job.alert_age_percent").with_payload(p20_payload).with_value(1.hour / 1.5.hours * 100)
170
+ .and emit_notification("delayed.job.count").with_payload(p30_payload).with_value(4)
171
+ .and emit_notification("delayed.job.future_count").with_payload(p30_payload).with_value(1)
172
+ .and emit_notification("delayed.job.locked_count").with_payload(p30_payload).with_value(2)
173
+ .and emit_notification("delayed.job.erroring_count").with_payload(p30_payload).with_value(1)
174
+ .and emit_notification("delayed.job.failed_count").with_payload(p30_payload).with_value(1)
175
+ .and emit_notification("delayed.job.working_count").with_payload(p30_payload).with_value(1)
176
+ .and emit_notification("delayed.job.workable_count").with_payload(p30_payload).with_value(1)
177
+ .and emit_notification("delayed.job.max_age").with_payload(p30_payload).with_value(6.hours)
178
+ .and emit_notification("delayed.job.max_lock_age").with_payload(p30_payload).with_value(11.minutes)
179
+ .and emit_notification("delayed.job.alert_age_percent").with_payload(p30_payload).with_value(100) # 6 hours / 4 hours (overflow)
180
+ .and emit_notification("delayed.job.workable_count").with_payload(p30_payload.merge(queue: 'banana')).with_value(1)
181
+ .and emit_notification("delayed.job.max_age").with_payload(p30_payload.merge(queue: 'banana')).with_value(4.hours)
182
+ end
183
+
184
+ context 'when named priorities are customized' do
185
+ around do |example|
186
+ Delayed::Priority.names = { high: 0, low: 20 }
187
+ example.run
188
+ ensure
189
+ Delayed::Priority.names = nil
190
+ end
191
+ let(:p0_payload) { default_payload.merge(priority: 'high') }
192
+ let(:p20_payload) { default_payload.merge(priority: 'low') }
193
+
194
+ it 'emits the expected results for each metric' do
195
+ expect { subject.run! }
196
+ .to emit_notification("delayed.monitor.run").with_payload(default_payload.except(:queue))
197
+ .and emit_notification("delayed.job.count").with_payload(p0_payload).with_value(8)
198
+ .and emit_notification("delayed.job.future_count").with_payload(p0_payload).with_value(2)
199
+ .and emit_notification("delayed.job.locked_count").with_payload(p0_payload).with_value(4)
200
+ .and emit_notification("delayed.job.erroring_count").with_payload(p0_payload).with_value(2)
201
+ .and emit_notification("delayed.job.failed_count").with_payload(p0_payload).with_value(2)
202
+ .and emit_notification("delayed.job.working_count").with_payload(p0_payload).with_value(2)
203
+ .and emit_notification("delayed.job.workable_count").with_payload(p0_payload).with_value(2)
204
+ .and emit_notification("delayed.job.max_age").with_payload(p0_payload).with_value(2.minutes)
205
+ .and emit_notification("delayed.job.max_lock_age").with_payload(p0_payload).with_value(7.minutes)
206
+ .and emit_notification("delayed.job.alert_age_percent").with_payload(p0_payload).with_value(0)
207
+ .and emit_notification("delayed.job.count").with_payload(p20_payload).with_value(8)
208
+ .and emit_notification("delayed.job.future_count").with_payload(p20_payload).with_value(2)
209
+ .and emit_notification("delayed.job.locked_count").with_payload(p20_payload).with_value(4)
210
+ .and emit_notification("delayed.job.erroring_count").with_payload(p20_payload).with_value(2)
211
+ .and emit_notification("delayed.job.failed_count").with_payload(p20_payload).with_value(2)
212
+ .and emit_notification("delayed.job.working_count").with_payload(p20_payload).with_value(2)
213
+ .and emit_notification("delayed.job.workable_count").with_payload(p20_payload).with_value(2)
214
+ .and emit_notification("delayed.job.max_age").with_payload(p20_payload).with_value(6.hours)
215
+ .and emit_notification("delayed.job.max_lock_age").with_payload(p20_payload).with_value(11.minutes)
216
+ .and emit_notification("delayed.job.alert_age_percent").with_payload(p20_payload).with_value(0)
217
+ .and emit_notification("delayed.job.workable_count").with_payload(p20_payload.merge(queue: 'banana')).with_value(1)
218
+ .and emit_notification("delayed.job.max_age").with_payload(p20_payload.merge(queue: 'banana')).with_value(4.hours)
219
+ end
220
+
221
+ context 'when alert thresholds are specified' do
222
+ around do |example|
223
+ Delayed::Priority.alerts = { high: { age: 3.hours }, low: { age: 1.year } }
224
+ example.run
225
+ ensure
226
+ Delayed::Priority.alerts = nil
227
+ end
228
+
229
+ it 'emits the expected alert_age_percent results' do
230
+ expect { subject.run! }
231
+ .to emit_notification("delayed.job.alert_age_percent").with_payload(p0_payload).with_value(2.0.minutes / 3.hours * 100)
232
+ .and emit_notification("delayed.job.alert_age_percent").with_payload(p20_payload).with_value(6.0.hours / 1.year * 100)
233
+ end
234
+ end
235
+ end
236
+
237
+ context 'when worker queues are specified' do
238
+ around do |example|
239
+ Delayed::Worker.queues = %w(banana gram)
240
+ Delayed::Priority.names = { interactive: 0 } # avoid splitting by priority for simplicity
241
+ Delayed::Priority.alerts = { interactive: { age: 8.hours } }
242
+ example.run
243
+ ensure
244
+ Delayed::Priority.names = nil
245
+ Delayed::Worker.queues = []
246
+ end
247
+ let(:banana_payload) { default_payload.merge(queue: 'banana', priority: 'interactive') }
248
+ let(:gram_payload) { default_payload.merge(queue: 'gram', priority: 'interactive') }
249
+
250
+ it 'emits the expected results for each queue' do
251
+ expect { subject.run! }
252
+ .to emit_notification("delayed.monitor.run").with_payload(default_payload.except(:queue))
253
+ .and emit_notification("delayed.job.count").with_payload(banana_payload).with_value(1)
254
+ .and emit_notification("delayed.job.future_count").with_payload(banana_payload).with_value(0)
255
+ .and emit_notification("delayed.job.locked_count").with_payload(banana_payload).with_value(0)
256
+ .and emit_notification("delayed.job.erroring_count").with_payload(banana_payload).with_value(0)
257
+ .and emit_notification("delayed.job.failed_count").with_payload(banana_payload).with_value(0)
258
+ .and emit_notification("delayed.job.working_count").with_payload(banana_payload).with_value(0)
259
+ .and emit_notification("delayed.job.workable_count").with_payload(banana_payload).with_value(1)
260
+ .and emit_notification("delayed.job.max_age").with_payload(banana_payload).with_value(4.hours)
261
+ .and emit_notification("delayed.job.max_lock_age").with_payload(banana_payload).with_value(0)
262
+ .and emit_notification("delayed.job.alert_age_percent").with_payload(banana_payload).with_value(4.0.hours / 8.hours * 100)
263
+ .and emit_notification("delayed.job.count").with_payload(gram_payload).with_value(0)
264
+ .and emit_notification("delayed.job.future_count").with_payload(gram_payload).with_value(0)
265
+ .and emit_notification("delayed.job.locked_count").with_payload(gram_payload).with_value(0)
266
+ .and emit_notification("delayed.job.erroring_count").with_payload(gram_payload).with_value(0)
267
+ .and emit_notification("delayed.job.failed_count").with_payload(gram_payload).with_value(0)
268
+ .and emit_notification("delayed.job.working_count").with_payload(gram_payload).with_value(0)
269
+ .and emit_notification("delayed.job.workable_count").with_payload(gram_payload).with_value(0)
270
+ .and emit_notification("delayed.job.max_age").with_payload(gram_payload).with_value(0)
271
+ .and emit_notification("delayed.job.max_lock_age").with_payload(gram_payload).with_value(0)
272
+ .and emit_notification("delayed.job.alert_age_percent").with_payload(gram_payload).with_value(0)
273
+ end
274
+ end
275
+ end
276
+ end
@@ -0,0 +1,49 @@
1
+ require 'helper'
2
+
3
+ RSpec.describe Delayed::Plugins::Instrumentation do
4
+ let!(:job) { Delayed::Job.enqueue SimpleJob.new, priority: 13, queue: 'test' }
5
+
6
+ it 'emits delayed.job.run' do
7
+ expect { Delayed::Worker.new.work_off }.to emit_notification('delayed.job.run').with_payload(
8
+ job_name: 'SimpleJob',
9
+ priority: 13,
10
+ queue: 'test',
11
+ table: 'delayed_jobs',
12
+ database: current_database,
13
+ database_adapter: current_adapter,
14
+ job: job,
15
+ )
16
+ end
17
+
18
+ context 'when the job errors' do
19
+ let!(:job) { Delayed::Job.enqueue ErrorJob.new, priority: 7, queue: 'foo' }
20
+
21
+ it 'emits delayed.job.error' do
22
+ expect { Delayed::Worker.new.work_off }.to emit_notification('delayed.job.error').with_payload(
23
+ job_name: 'ErrorJob',
24
+ priority: 7,
25
+ queue: 'foo',
26
+ table: 'delayed_jobs',
27
+ database: current_database,
28
+ database_adapter: current_adapter,
29
+ job: job,
30
+ )
31
+ end
32
+ end
33
+
34
+ context 'when the job fails' do
35
+ let!(:job) { Delayed::Job.enqueue FailureJob.new, priority: 3, queue: 'bar' }
36
+
37
+ it 'emits delayed.job.failure' do
38
+ expect { Delayed::Worker.new.work_off }.to emit_notification('delayed.job.failure').with_payload(
39
+ job_name: 'FailureJob',
40
+ priority: 3,
41
+ queue: 'bar',
42
+ table: 'delayed_jobs',
43
+ database: current_database,
44
+ database_adapter: current_adapter,
45
+ job: job,
46
+ )
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,154 @@
1
+ require 'helper'
2
+
3
+ RSpec.describe Delayed::Priority do
4
+ let(:custom_names) { nil }
5
+ let(:custom_alerts) { nil }
6
+
7
+ around do |example|
8
+ described_class.names = custom_names
9
+ described_class.alerts = custom_alerts
10
+ example.run
11
+ ensure
12
+ described_class.alerts = nil
13
+ described_class.names = nil
14
+ end
15
+
16
+ describe '.names, .ranges, .alerts, method_missing' do
17
+ it 'defaults to interactive, user_visible, eventual, reporting' do
18
+ expect(described_class.names).to eq(
19
+ interactive: 0,
20
+ user_visible: 10,
21
+ eventual: 20,
22
+ reporting: 30,
23
+ )
24
+ expect(described_class.ranges).to eq(
25
+ interactive: (0...10),
26
+ user_visible: (10...20),
27
+ eventual: (20...30),
28
+ reporting: (30...Float::INFINITY),
29
+ )
30
+ expect(described_class.alerts).to eq(
31
+ interactive: { age: 1.minute, run_time: 30.seconds, attempts: 3 },
32
+ user_visible: { age: 3.minutes, run_time: 90.seconds, attempts: 5 },
33
+ eventual: { age: 1.5.hours, run_time: 5.minutes, attempts: 8 },
34
+ reporting: { age: 4.hours, run_time: 10.minutes, attempts: 8 },
35
+ )
36
+ expect(described_class).to respond_to(:interactive)
37
+ expect(described_class).to respond_to(:user_visible)
38
+ expect(described_class).to respond_to(:eventual)
39
+ expect(described_class).to respond_to(:reporting)
40
+ expect(described_class.interactive).to eq 0
41
+ expect(described_class.user_visible).to eq 10
42
+ expect(described_class.eventual).to eq 20
43
+ expect(described_class.reporting).to eq 30
44
+ end
45
+
46
+ context 'when customized to high, medium, low' do
47
+ let(:custom_names) { { high: 0, medium: 100, low: 500 } }
48
+
49
+ it 'returns the customized value' do
50
+ expect(described_class.names).to eq(
51
+ high: 0,
52
+ medium: 100,
53
+ low: 500,
54
+ )
55
+ expect(described_class.ranges).to eq(
56
+ high: (0...100),
57
+ medium: (100...500),
58
+ low: (500...Float::INFINITY),
59
+ )
60
+ expect(described_class.alerts).to eq({})
61
+ expect(described_class).not_to respond_to(:interactive)
62
+ expect(described_class).not_to respond_to(:user_visible)
63
+ expect(described_class).not_to respond_to(:eventual)
64
+ expect(described_class).not_to respond_to(:reporting)
65
+ expect(described_class).to respond_to(:high)
66
+ expect(described_class).to respond_to(:medium)
67
+ expect(described_class).to respond_to(:low)
68
+ expect(described_class.high).to eq 0
69
+ expect(described_class.medium).to eq 100
70
+ expect(described_class.low).to eq 500
71
+ end
72
+
73
+ context 'when custom alert thresholds are defined' do
74
+ let(:custom_alerts) { { high: { age: 1.minute }, medium: { run_time: 1.minute }, low: { attempts: 10 } } }
75
+
76
+ it 'returns the customized value' do
77
+ expect(described_class.alerts).to eq(
78
+ high: { age: 1.minute },
79
+ medium: { run_time: 1.minute },
80
+ low: { attempts: 10 },
81
+ )
82
+ end
83
+ end
84
+ end
85
+ end
86
+
87
+ it 'provides the name of the priority range' do
88
+ expect(described_class.new(0).name).to eq :interactive
89
+ expect(described_class.new(3).name).to eq :interactive
90
+ expect(described_class.new(10).name).to eq :user_visible
91
+ expect(described_class.new(29).name).to eq :eventual
92
+ expect(described_class.new(999).name).to eq :reporting
93
+ expect(described_class.new(-123).name).to eq nil
94
+ end
95
+
96
+ it 'supports initialization by symbol value' do
97
+ expect(described_class.new(:interactive)).to eq(0)
98
+ expect(described_class.new(:user_visible)).to eq(10)
99
+ expect(described_class.new(:eventual)).to eq(20)
100
+ expect(described_class.new(:reporting)).to eq(30)
101
+ end
102
+
103
+ it "supports predicate ('?') methods" do
104
+ expect(described_class.new(0).interactive?).to eq true
105
+ expect(described_class.new(3)).to be_interactive
106
+ expect(described_class.new(3).user_visible?).to eq false
107
+ expect(described_class.new(10)).to be_user_visible
108
+ expect(described_class.new(29)).to be_eventual
109
+ expect(described_class.new(999)).to be_reporting
110
+ expect(described_class.new(-123).interactive?).to eq false
111
+ end
112
+
113
+ it 'supports alert threshold methods' do
114
+ described_class.alerts = {
115
+ interactive: { age: 77.seconds },
116
+ user_visible: { run_time: 11.seconds },
117
+ eventual: { attempts: 7 },
118
+ }
119
+ expect(described_class.interactive.alert_age).to eq 77.seconds
120
+ expect(described_class.user_visible.alert_run_time).to eq 11.seconds
121
+ expect(described_class.eventual.alert_attempts).to eq 7
122
+ expect(described_class.reporting.alert_age).to be_nil
123
+ expect(described_class.reporting.alert_run_time).to be_nil
124
+ expect(described_class.reporting.alert_attempts).to be_nil
125
+ ensure
126
+ described_class.alerts = nil
127
+ end
128
+
129
+ it 'supports comparisons' do
130
+ expect(described_class.new(3)).to be < described_class.new(5)
131
+ expect(described_class.new(10)).to be >= described_class.new(10)
132
+ expect(described_class.new(101)).to eq described_class.new(101) # rubocop:disable RSpec/IdenticalEqualityAssertion
133
+ end
134
+
135
+ it 'suports coercion' do
136
+ expect(described_class.new(0)).to eq 0
137
+ expect(described_class.new(8)).to be > 5
138
+ expect(described_class.new(5)).to be < 8
139
+ expect(0 == described_class.new(0)).to eq true
140
+ expect(8 > described_class.new(5)).to eq true
141
+ expect(5 < described_class.new(8)).to eq true
142
+ end
143
+
144
+ it 'supports sorting' do
145
+ expect(
146
+ [
147
+ described_class.new(5),
148
+ described_class.new(40),
149
+ described_class.new(3),
150
+ described_class.new(-13),
151
+ ].sort,
152
+ ).to eq [-13, 3, 5, 40]
153
+ end
154
+ end