sidekiq-failures-discourse 0.3.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.
- checksums.yaml +7 -0
- data/.gitignore +16 -0
- data/.travis.yml +7 -0
- data/CHANGELOG.md +48 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +51 -0
- data/LICENSE +22 -0
- data/README.md +153 -0
- data/Rakefile +12 -0
- data/lib/sidekiq-failures.rb +1 -0
- data/lib/sidekiq/failures.rb +79 -0
- data/lib/sidekiq/failures/failure_set.rb +22 -0
- data/lib/sidekiq/failures/locales/en.yml +5 -0
- data/lib/sidekiq/failures/middleware.rb +99 -0
- data/lib/sidekiq/failures/sorted_entry.rb +14 -0
- data/lib/sidekiq/failures/version.rb +5 -0
- data/lib/sidekiq/failures/views/failure.erb +30 -0
- data/lib/sidekiq/failures/views/failures.erb +75 -0
- data/lib/sidekiq/failures/web_extension.rb +72 -0
- data/sidekiq-failures.gemspec +24 -0
- data/test/middleware_test.rb +283 -0
- data/test/test_helper.rb +20 -0
- data/test/web_extension_test.rb +209 -0
- metadata +139 -0
@@ -0,0 +1,14 @@
|
|
1
|
+
module Sidekiq
|
2
|
+
class SortedEntry
|
3
|
+
def retry_failure
|
4
|
+
Sidekiq.redis do |conn|
|
5
|
+
results = conn.zrangebyscore(Sidekiq::Failures::LIST_KEY, score, score)
|
6
|
+
conn.zremrangebyscore(Sidekiq::Failures::LIST_KEY, score, score)
|
7
|
+
results.map do |message|
|
8
|
+
msg = Sidekiq.load_json(message)
|
9
|
+
Sidekiq::Client.push(msg)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
<%= erb :_job_info, :locals => {:job => @failure, :type => :failure} %>
|
2
|
+
|
3
|
+
<h3><%= t('Error') %></h3>
|
4
|
+
<table class="error table table-bordered table-striped">
|
5
|
+
<tbody>
|
6
|
+
<tr>
|
7
|
+
<th><%= t('ErrorClass') %></th>
|
8
|
+
<td>
|
9
|
+
<code><%= @failure['error_class'] %></code>
|
10
|
+
</td>
|
11
|
+
</tr>
|
12
|
+
<tr>
|
13
|
+
<th><%= t('ErrorMessage') %></th>
|
14
|
+
<td><%= @failure['error_message'] %></td>
|
15
|
+
</tr>
|
16
|
+
<% if !@failure['error_backtrace'].nil? %>
|
17
|
+
<tr>
|
18
|
+
<th><%= t('ErrorBacktrace') %></th>
|
19
|
+
<td>
|
20
|
+
<code><%= @failure['error_backtrace'].join("<br/>") %></code>
|
21
|
+
</td>
|
22
|
+
</tr>
|
23
|
+
<% end %>
|
24
|
+
</tbody>
|
25
|
+
</table>
|
26
|
+
<form class="form-horizontal" action="<%= root_path %>failures/<%= job_params(@failure, @failure.score) %>" method="post">
|
27
|
+
<a class="btn" href="<%= root_path %>failures"><%= t('GoBack') %></a>
|
28
|
+
<input class="btn btn-primary" type="submit" name="retry" value="<%= t('RetryNow') %>" />
|
29
|
+
<input class="btn btn-danger" type="submit" name="delete" value="<%= t('Delete') %>" />
|
30
|
+
</form>
|
@@ -0,0 +1,75 @@
|
|
1
|
+
<header class="row">
|
2
|
+
<div class="col-sm-5">
|
3
|
+
<h3><%= t('FailedJobs') %></h3>
|
4
|
+
</div>
|
5
|
+
<% if @failures.size > 0 && @total_size > @count %>
|
6
|
+
<div class="col-sm-4">
|
7
|
+
<%= erb :_paging, :locals => { :url => "#{root_path}failures" } %>
|
8
|
+
</div>
|
9
|
+
<% end %>
|
10
|
+
<%= filtering('failures') %>
|
11
|
+
</header>
|
12
|
+
|
13
|
+
<% if @failures.size > 0 %>
|
14
|
+
<form action="<%= root_path %>failures" method="post">
|
15
|
+
<table class="table table-striped table-bordered table-white">
|
16
|
+
<thead>
|
17
|
+
<tr>
|
18
|
+
<th width="20px" class="table-checkbox">
|
19
|
+
<label>
|
20
|
+
<input type="checkbox" class="check_all" />
|
21
|
+
</label>
|
22
|
+
</th>
|
23
|
+
<th style="width: 20%"><%= t('Worker') %></th>
|
24
|
+
<th style="width: 10%"><%= t('Arguments') %></th>
|
25
|
+
<th style="width: 10%"><%= t('Queue') %></th>
|
26
|
+
<th style="width: 10%"><%= t('FailedAt') %></th>
|
27
|
+
<th style="width: 50%"><%= t('Error') %></th>
|
28
|
+
</tr>
|
29
|
+
</thead>
|
30
|
+
<% @failures.each do |entry| %>
|
31
|
+
<tr>
|
32
|
+
<td class="table-checkbox">
|
33
|
+
<label>
|
34
|
+
<input type='checkbox' name='key[]' value='<%= job_params(entry.item, entry.score) %>' />
|
35
|
+
</label>
|
36
|
+
</td>
|
37
|
+
<td><a href="<%= root_path %>failures/<%= job_params(entry.item, entry.score) %>"><%= entry.klass %></a></td>
|
38
|
+
<td>
|
39
|
+
<div class="args"><%= display_args(entry.args) %></div>
|
40
|
+
</td>
|
41
|
+
<td>
|
42
|
+
<a href="<%= root_path %>queues/<%= entry.queue %>"><%= entry.queue %></a>
|
43
|
+
</td>
|
44
|
+
<td><%= relative_time(Time.at(entry['failed_at'])) %></td>
|
45
|
+
<td style="overflow: auto; padding: 10px;">
|
46
|
+
<a class="backtrace" href="#" onclick="$(this).next().toggle(); return false">
|
47
|
+
<%= h entry['error_class'] %>: <%= h entry['error_message'] %>
|
48
|
+
</a>
|
49
|
+
<pre style="display: none; background: none; border: 0; width: 100%; max-height: 30em; font-size: 0.8em; white-space: nowrap;">
|
50
|
+
<%= entry['error_backtrace'].join("<br />") if entry['error_backtrace'] %>
|
51
|
+
</pre>
|
52
|
+
<p>
|
53
|
+
<span>Processor: <%= entry['processor'] %></span>
|
54
|
+
</p>
|
55
|
+
</td>
|
56
|
+
</tr>
|
57
|
+
<% end %>
|
58
|
+
</table>
|
59
|
+
<input class="btn btn-primary btn-xs pull-left" type="submit" name="retry" value="<%= t('RetryNow') %>" />
|
60
|
+
<input class="btn btn-danger btn-xs pull-left" type="submit" name="delete" value="<%= t('Delete') %>" />
|
61
|
+
</form>
|
62
|
+
|
63
|
+
<form action="<%= root_path %>failures/all/reset" method="post">
|
64
|
+
<input class="btn btn-danger btn-xs pull-right" type="submit" name="reset" value="<%= t('Reset Counter') %>" data-confirm="<%= t('AreYouSure') %>" />
|
65
|
+
</form>
|
66
|
+
<form action="<%= root_path %>failures/all/delete" method="post">
|
67
|
+
<input class="btn btn-danger btn-xs pull-right" type="submit" name="delete" value="<%= t('DeleteAll') %>" data-confirm="<%= t('AreYouSure') %>" />
|
68
|
+
</form>
|
69
|
+
<form action="<%= root_path %>failures/all/retry" method="post">
|
70
|
+
<input class="btn btn-danger btn-xs pull-right" type="submit" name="retry" value="<%= t('RetryAll') %>" data-confirm="<%= t('AreYouSure') %>" />
|
71
|
+
</form>
|
72
|
+
|
73
|
+
<% else %>
|
74
|
+
<div class="alert alert-success"><%= t('NoFailedJobsFound') %></div>
|
75
|
+
<% end %>
|
@@ -0,0 +1,72 @@
|
|
1
|
+
module Sidekiq
|
2
|
+
module Failures
|
3
|
+
module WebExtension
|
4
|
+
|
5
|
+
def self.registered(app)
|
6
|
+
view_path = File.join(File.expand_path("..", __FILE__), "views")
|
7
|
+
|
8
|
+
app.get "/failures" do
|
9
|
+
@count = (params[:count] || 25).to_i
|
10
|
+
(@current_page, @total_size, @failures) = page(LIST_KEY, params[:page], @count)
|
11
|
+
@failures = @failures.map {|msg, score| Sidekiq::SortedEntry.new(nil, score, msg) }
|
12
|
+
|
13
|
+
render(:erb, File.read(File.join(view_path, "failures.erb")))
|
14
|
+
end
|
15
|
+
|
16
|
+
app.get "/failures/:key" do
|
17
|
+
halt 404 unless params['key']
|
18
|
+
|
19
|
+
@failure = FailureSet.new.fetch(*parse_params(params['key'])).first
|
20
|
+
redirect "#{root_path}failures" if @failure.nil?
|
21
|
+
render(:erb, File.read(File.join(view_path, "failure.erb")))
|
22
|
+
end
|
23
|
+
|
24
|
+
app.post "/failures" do
|
25
|
+
halt 404 unless params['key']
|
26
|
+
|
27
|
+
params['key'].each do |key|
|
28
|
+
job = FailureSet.new.fetch(*parse_params(key)).first
|
29
|
+
next unless job
|
30
|
+
|
31
|
+
if params['retry']
|
32
|
+
job.retry_failure
|
33
|
+
elsif params['delete']
|
34
|
+
job.delete
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
redirect_with_query("#{root_path}failures")
|
39
|
+
end
|
40
|
+
|
41
|
+
app.post "/failures/:key" do
|
42
|
+
halt 404 unless params['key']
|
43
|
+
|
44
|
+
job = FailureSet.new.fetch(*parse_params(params['key'])).first
|
45
|
+
if job
|
46
|
+
if params['retry']
|
47
|
+
job.retry_failure
|
48
|
+
elsif params['delete']
|
49
|
+
job.delete
|
50
|
+
end
|
51
|
+
end
|
52
|
+
redirect_with_query("#{root_path}failures")
|
53
|
+
end
|
54
|
+
|
55
|
+
app.post "/failures/all/reset" do
|
56
|
+
Sidekiq::Failures.reset_failures
|
57
|
+
redirect "#{root_path}failures"
|
58
|
+
end
|
59
|
+
|
60
|
+
app.post "/failures/all/delete" do
|
61
|
+
FailureSet.new.clear
|
62
|
+
redirect "#{root_path}failures"
|
63
|
+
end
|
64
|
+
|
65
|
+
app.post "/failures/all/retry" do
|
66
|
+
FailureSet.new.retry_all_failures
|
67
|
+
redirect "#{root_path}failures"
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/sidekiq/failures/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.authors = ["Marcelo Silveira"]
|
6
|
+
gem.email = ["marcelo@mhfs.com.br"]
|
7
|
+
gem.description = %q{Keep track of Sidekiq failed jobs}
|
8
|
+
gem.summary = %q{Keeps track of Sidekiq failed jobs and adds a tab to the Web UI to let you browse them. Makes use of Sidekiq's custom tabs and middleware chain.}
|
9
|
+
gem.homepage = "https://github.com/mhfs/sidekiq-failures/"
|
10
|
+
|
11
|
+
gem.files = `git ls-files`.split($\)
|
12
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
13
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
14
|
+
gem.name = "sidekiq-failures-discourse"
|
15
|
+
gem.require_paths = ["lib"]
|
16
|
+
gem.version = Sidekiq::Failures::VERSION
|
17
|
+
|
18
|
+
gem.add_dependency "sidekiq", ">= 2.14.0"
|
19
|
+
|
20
|
+
gem.add_development_dependency "rake"
|
21
|
+
gem.add_development_dependency "rack-test"
|
22
|
+
gem.add_development_dependency "sprockets"
|
23
|
+
gem.add_development_dependency "sinatra"
|
24
|
+
end
|
@@ -0,0 +1,283 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
|
3
|
+
module Sidekiq
|
4
|
+
module Failures
|
5
|
+
describe "Middleware" do
|
6
|
+
before do
|
7
|
+
Celluloid.boot
|
8
|
+
$invokes = 0
|
9
|
+
@boss = MiniTest::Mock.new
|
10
|
+
@processor = ::Sidekiq::Processor.new(@boss)
|
11
|
+
Sidekiq.server_middleware {|chain| chain.add Sidekiq::Failures::Middleware }
|
12
|
+
Sidekiq.redis = REDIS
|
13
|
+
Sidekiq.redis { |c| c.flushdb }
|
14
|
+
Sidekiq.instance_eval { @failures_default_mode = nil }
|
15
|
+
end
|
16
|
+
|
17
|
+
TestException = Class.new(Exception)
|
18
|
+
ShutdownException = Class.new(Sidekiq::Shutdown)
|
19
|
+
|
20
|
+
class MockWorker
|
21
|
+
include Sidekiq::Worker
|
22
|
+
|
23
|
+
def perform(args)
|
24
|
+
$invokes += 1
|
25
|
+
raise ShutdownException.new if args == "shutdown"
|
26
|
+
raise TestException.new("failed!")
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'raises an error when failures_default_mode is configured incorrectly' do
|
31
|
+
assert_raises ArgumentError do
|
32
|
+
Sidekiq.failures_default_mode = 'exhaustion'
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'defaults failures_default_mode to all' do
|
37
|
+
assert_equal :all, Sidekiq.failures_default_mode
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'records all failures by default' do
|
41
|
+
msg = create_work('class' => MockWorker.to_s, 'args' => ['myarg'])
|
42
|
+
|
43
|
+
assert_equal 0, failures_count
|
44
|
+
|
45
|
+
actor = MiniTest::Mock.new
|
46
|
+
actor.expect(:processor_done, nil, [@processor])
|
47
|
+
actor.expect(:real_thread, nil, [nil, Celluloid::Thread])
|
48
|
+
2.times { @boss.expect(:async, actor, []) }
|
49
|
+
|
50
|
+
assert_raises TestException do
|
51
|
+
@processor.process(msg)
|
52
|
+
end
|
53
|
+
|
54
|
+
assert_equal 1, failures_count
|
55
|
+
assert_equal 1, $invokes
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'records all failures if explicitly told to' do
|
59
|
+
msg = create_work('class' => MockWorker.to_s, 'args' => ['myarg'], 'failures' => true)
|
60
|
+
|
61
|
+
assert_equal 0, failures_count
|
62
|
+
|
63
|
+
actor = MiniTest::Mock.new
|
64
|
+
actor.expect(:processor_done, nil, [@processor])
|
65
|
+
actor.expect(:real_thread, nil, [nil, Celluloid::Thread])
|
66
|
+
2.times { @boss.expect(:async, actor, []) }
|
67
|
+
|
68
|
+
assert_raises TestException do
|
69
|
+
@processor.process(msg)
|
70
|
+
end
|
71
|
+
|
72
|
+
assert_equal 1, failures_count
|
73
|
+
assert_equal 1, $invokes
|
74
|
+
end
|
75
|
+
|
76
|
+
it "doesn't record internal shutdown failure" do
|
77
|
+
msg = create_work('class' => MockWorker.to_s, 'args' => ['shutdown'], 'failures' => true)
|
78
|
+
|
79
|
+
assert_equal 0, failures_count
|
80
|
+
|
81
|
+
actor = MiniTest::Mock.new
|
82
|
+
actor.expect(:processor_done, nil, [@processor])
|
83
|
+
actor.expect(:real_thread, nil, [nil, Celluloid::Thread])
|
84
|
+
2.times { @boss.expect(:async, actor, []) }
|
85
|
+
|
86
|
+
@processor.process(msg)
|
87
|
+
@boss.verify
|
88
|
+
|
89
|
+
assert_equal 0, failures_count
|
90
|
+
assert_equal 1, $invokes
|
91
|
+
end
|
92
|
+
|
93
|
+
it "doesn't record failure if failures disabled" do
|
94
|
+
msg = create_work('class' => MockWorker.to_s, 'args' => ['myarg'], 'failures' => false)
|
95
|
+
|
96
|
+
assert_equal 0, failures_count
|
97
|
+
|
98
|
+
actor = MiniTest::Mock.new
|
99
|
+
actor.expect(:processor_done, nil, [@processor])
|
100
|
+
actor.expect(:real_thread, nil, [nil, Celluloid::Thread])
|
101
|
+
2.times { @boss.expect(:async, actor, []) }
|
102
|
+
|
103
|
+
assert_raises TestException do
|
104
|
+
@processor.process(msg)
|
105
|
+
end
|
106
|
+
|
107
|
+
assert_equal 0, failures_count
|
108
|
+
assert_equal 1, $invokes
|
109
|
+
end
|
110
|
+
|
111
|
+
it "doesn't record failure if going to be retired again and configured to track exhaustion by default" do
|
112
|
+
Sidekiq.failures_default_mode = :exhausted
|
113
|
+
|
114
|
+
msg = create_work('class' => MockWorker.to_s, 'args' => ['myarg'], 'retry' => true )
|
115
|
+
|
116
|
+
assert_equal 0, failures_count
|
117
|
+
|
118
|
+
actor = MiniTest::Mock.new
|
119
|
+
actor.expect(:processor_done, nil, [@processor])
|
120
|
+
actor.expect(:real_thread, nil, [nil, Celluloid::Thread])
|
121
|
+
2.times { @boss.expect(:async, actor, []) }
|
122
|
+
|
123
|
+
assert_raises TestException do
|
124
|
+
@processor.process(msg)
|
125
|
+
end
|
126
|
+
|
127
|
+
assert_equal 0, failures_count
|
128
|
+
assert_equal 1, $invokes
|
129
|
+
end
|
130
|
+
|
131
|
+
|
132
|
+
it "doesn't record failure if going to be retired again and configured to track exhaustion" do
|
133
|
+
msg = create_work('class' => MockWorker.to_s, 'args' => ['myarg'], 'retry' => true, 'failures' => 'exhausted')
|
134
|
+
|
135
|
+
assert_equal 0, failures_count
|
136
|
+
|
137
|
+
actor = MiniTest::Mock.new
|
138
|
+
actor.expect(:processor_done, nil, [@processor])
|
139
|
+
actor.expect(:real_thread, nil, [nil, Celluloid::Thread])
|
140
|
+
2.times { @boss.expect(:async, actor, []) }
|
141
|
+
|
142
|
+
assert_raises TestException do
|
143
|
+
@processor.process(msg)
|
144
|
+
end
|
145
|
+
|
146
|
+
assert_equal 0, failures_count
|
147
|
+
assert_equal 1, $invokes
|
148
|
+
end
|
149
|
+
|
150
|
+
it "records failure if failing last retry and configured to track exhaustion" do
|
151
|
+
msg = create_work('class' => MockWorker.to_s, 'args' => ['myarg'], 'retry' => true, 'retry_count' => 25, 'failures' => 'exhausted')
|
152
|
+
|
153
|
+
assert_equal 0, failures_count
|
154
|
+
|
155
|
+
actor = MiniTest::Mock.new
|
156
|
+
actor.expect(:processor_done, nil, [@processor])
|
157
|
+
actor.expect(:real_thread, nil, [nil, Celluloid::Thread])
|
158
|
+
2.times { @boss.expect(:async, actor, []) }
|
159
|
+
|
160
|
+
assert_raises TestException do
|
161
|
+
@processor.process(msg)
|
162
|
+
end
|
163
|
+
|
164
|
+
assert_equal 1, failures_count
|
165
|
+
assert_equal 1, $invokes
|
166
|
+
end
|
167
|
+
|
168
|
+
it "records failure if retry disabled and configured to track exhaustion" do
|
169
|
+
msg = create_work('class' => MockWorker.to_s, 'args' => ['myarg'], 'retry' => false, 'failures' => 'exhausted')
|
170
|
+
|
171
|
+
assert_equal 0, failures_count
|
172
|
+
|
173
|
+
actor = MiniTest::Mock.new
|
174
|
+
actor.expect(:processor_done, nil, [@processor])
|
175
|
+
actor.expect(:real_thread, nil, [nil, Celluloid::Thread])
|
176
|
+
2.times { @boss.expect(:async, actor, []) }
|
177
|
+
|
178
|
+
assert_raises TestException do
|
179
|
+
@processor.process(msg)
|
180
|
+
end
|
181
|
+
|
182
|
+
assert_equal 1, failures_count
|
183
|
+
assert_equal 1, $invokes
|
184
|
+
end
|
185
|
+
|
186
|
+
it "records failure if retry disabled and configured to track exhaustion by default" do
|
187
|
+
Sidekiq.failures_default_mode = 'exhausted'
|
188
|
+
|
189
|
+
msg = create_work('class' => MockWorker.to_s, 'args' => ['myarg'], 'retry' => false)
|
190
|
+
|
191
|
+
assert_equal 0, failures_count
|
192
|
+
|
193
|
+
actor = MiniTest::Mock.new
|
194
|
+
actor.expect(:processor_done, nil, [@processor])
|
195
|
+
actor.expect(:real_thread, nil, [nil, Celluloid::Thread])
|
196
|
+
2.times { @boss.expect(:async, actor, []) }
|
197
|
+
|
198
|
+
assert_raises TestException do
|
199
|
+
@processor.process(msg)
|
200
|
+
end
|
201
|
+
|
202
|
+
assert_equal 1, failures_count
|
203
|
+
assert_equal 1, $invokes
|
204
|
+
end
|
205
|
+
|
206
|
+
it "records failure if failing last retry and configured to track exhaustion by default" do
|
207
|
+
Sidekiq.failures_default_mode = 'exhausted'
|
208
|
+
|
209
|
+
msg = create_work('class' => MockWorker.to_s, 'args' => ['myarg'], 'retry' => true, 'retry_count' => 25)
|
210
|
+
|
211
|
+
assert_equal 0, failures_count
|
212
|
+
|
213
|
+
actor = MiniTest::Mock.new
|
214
|
+
actor.expect(:processor_done, nil, [@processor])
|
215
|
+
actor.expect(:real_thread, nil, [nil, Celluloid::Thread])
|
216
|
+
2.times { @boss.expect(:async, actor, []) }
|
217
|
+
|
218
|
+
assert_raises TestException do
|
219
|
+
@processor.process(msg)
|
220
|
+
end
|
221
|
+
|
222
|
+
assert_equal 1, failures_count
|
223
|
+
assert_equal 1, $invokes
|
224
|
+
end
|
225
|
+
|
226
|
+
it "removes old failures when failures_max_count has been reached" do
|
227
|
+
assert_equal 1000, Sidekiq.failures_max_count
|
228
|
+
Sidekiq.failures_max_count = 2
|
229
|
+
|
230
|
+
msg = create_work('class' => MockWorker.to_s, 'args' => ['myarg'])
|
231
|
+
|
232
|
+
assert_equal 0, failures_count
|
233
|
+
|
234
|
+
3.times do
|
235
|
+
boss = MiniTest::Mock.new
|
236
|
+
processor = ::Sidekiq::Processor.new(boss)
|
237
|
+
|
238
|
+
actor = MiniTest::Mock.new
|
239
|
+
actor.expect(:processor_done, nil, [processor])
|
240
|
+
actor.expect(:real_thread, nil, [nil, Celluloid::Thread])
|
241
|
+
2.times { boss.expect(:async, actor, []) }
|
242
|
+
|
243
|
+
assert_raises TestException do
|
244
|
+
processor.process(msg)
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
assert_equal 2, failures_count
|
249
|
+
|
250
|
+
Sidekiq.failures_max_count = false
|
251
|
+
assert Sidekiq.failures_max_count == false
|
252
|
+
|
253
|
+
Sidekiq.failures_max_count = nil
|
254
|
+
assert_equal 1000, Sidekiq.failures_max_count
|
255
|
+
end
|
256
|
+
|
257
|
+
it 'returns the total number of failed jobs in the queue' do
|
258
|
+
msg = create_work('class' => MockWorker.to_s, 'args' => ['myarg'], 'failures' => true)
|
259
|
+
|
260
|
+
assert_equal 0, Sidekiq::Failures.count
|
261
|
+
|
262
|
+
actor = MiniTest::Mock.new
|
263
|
+
actor.expect(:processor_done, nil, [@processor])
|
264
|
+
actor.expect(:real_thread, nil, [nil, Celluloid::Thread])
|
265
|
+
@boss.expect(:async, actor, [])
|
266
|
+
|
267
|
+
assert_raises TestException do
|
268
|
+
@processor.process(msg)
|
269
|
+
end
|
270
|
+
|
271
|
+
assert_equal 1, Sidekiq::Failures.count
|
272
|
+
end
|
273
|
+
|
274
|
+
def failures_count
|
275
|
+
Sidekiq.redis { |conn| conn.zcard(LIST_KEY) }
|
276
|
+
end
|
277
|
+
|
278
|
+
def create_work(msg)
|
279
|
+
Sidekiq::BasicFetch::UnitOfWork.new('default', Sidekiq.dump_json(msg))
|
280
|
+
end
|
281
|
+
end
|
282
|
+
end
|
283
|
+
end
|