delayed 0.1.0

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 (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