sidekiq-status 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,19 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+
19
+ .idea/
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --color
2
+ --format documentation
3
+
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Evgeniy Tsvigun
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,61 @@
1
+ # Sidekiq::Status
2
+
3
+ An extension to [Sidekiq](http://github.com/mperham/sidekiq) message processing to track your jobs. Inspired
4
+ by [resque-status](http://github.com/quirkey/resque-status) and mostly copying its features, using Sidekiq's middleware.
5
+
6
+ ## Installation
7
+
8
+ gem install sidekiq-status
9
+
10
+ ## Usage
11
+
12
+ Configure your middleware chains, lookup [Middleware usage](https://github.com/mperham/sidekiq/wiki/Middleware)
13
+ on Sidekiq wiki for more info.
14
+
15
+ ``` ruby
16
+ require 'sidekiq'
17
+ require 'sidekiq-status'
18
+
19
+ Sidekiq.configure_client do |config|
20
+ config.client_middleware do |chain|
21
+ chain.add Sidekiq::Status::ClientMiddleware
22
+ end
23
+ end
24
+
25
+ Sidekiq.configure_server do |config|
26
+ config.server_middleware do |chain|
27
+ chain.add Sidekiq::Status::ServerMiddleware
28
+ end
29
+ end
30
+ ```
31
+
32
+ When defining those jobs you want to track later, include one more module. Jobs defined without Sidekiq::Status::Worker
33
+ will be processed as usual.
34
+
35
+ ``` ruby
36
+ class MyJob
37
+ include Sidekiq::Worker
38
+ include Sidekiq::Status::Worker
39
+
40
+ def perform(*args)
41
+ # your code goes here
42
+ end
43
+ end
44
+ ```
45
+
46
+ Query for job status any time later:
47
+
48
+ ``` ruby
49
+ job_id = MyJob.perform_async(*args)
50
+ # "queued", "working", "complete" or "failed" , nil after expiry (30 minutes)
51
+ status = Sidekiq::Status::get(job_id)
52
+ ```
53
+
54
+ ### Features coming
55
+ * Progress tracking, messages from running jobs
56
+ * Stopping jobs by id
57
+ * Minimal web UI
58
+
59
+ ## License
60
+ MIT License , see LICENSE for more details.
61
+ © 2012 Evgeniy Tsvigun
data/Rakefile ADDED
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
3
+
4
+ require 'rspec/core/rake_task'
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+ task :test => :spec
8
+
9
+ task :default => :spec
@@ -0,0 +1,16 @@
1
+ module Sidekiq::Status
2
+ # Should be in the client middleware chain
3
+ class ClientMiddleware
4
+ include Storage
5
+ # Uses the first argument as id and puts :queued status in the job's Redis hash
6
+ # @param [Class] worker_class if includes Sidekiq::Status::Worker, the job gets processed with the plugin
7
+ # @param [Array] msg job arguments, the firs one becomes the id
8
+ # @param [String] queue the queue's name
9
+ def call(worker_class, msg, queue)
10
+ if worker_class.include? Worker
11
+ store_for_id(msg['args'][0], :status => :queued)
12
+ end
13
+ yield
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,46 @@
1
+ module Sidekiq::Status
2
+ # Should be in the server middleware chain
3
+ class ServerMiddleware
4
+ # Parameterized initialization, use it when adding middleware to server chain
5
+ # chain.add Sidekiq::Status::ServerMiddleware, :expiration => 60 * 5
6
+ # @param [Hash] opts middleware initialization options
7
+ # @option opts [Fixnum] :expiration ttl for complete jobs
8
+ def initialize(opts = {:expiration => 30 * 60})
9
+ @expiration = opts[:expiration]
10
+ end
11
+
12
+ # Takes out the first job argument to use as id
13
+ # puts :working status into Redis hash
14
+ # initializes worker instance with id
15
+ #
16
+ # Exception handler sets :failed status, re-inserts worker it to job args and re-throws the exception
17
+ # Worker::Stopped exception type are processed separately - :stopped status is set, no re-throwing
18
+ #
19
+ # @param [Worker] worker worker instance, processed here if its class includes Status::Worker
20
+ # @param [Array] msg job args, first of them used as job id, should have uuid format
21
+ # @param [String] queue queue name
22
+ def call(worker, msg, queue)
23
+ if worker.is_a? Worker
24
+ worker.id = msg['args'].shift
25
+ unless worker.id.is_a?(String) && UUID_REGEXP.match(worker.id)
26
+ raise ArgumentError, "First job argument for a #{worker.class.name} should have uuid format"
27
+ end
28
+ worker.store 'status' => 'working'
29
+ yield
30
+ worker.store 'status' => 'complete'
31
+ else
32
+ yield
33
+ end
34
+ rescue Worker::Stopped
35
+ worker.store 'status' => 'stopped'
36
+ rescue
37
+ if worker.is_a? Worker
38
+ worker.store 'status' => 'failed'
39
+ msg['args'].unshift worker.id
40
+ end
41
+ raise
42
+ ensure
43
+ Sidekiq.redis { |conn| conn.expire worker.id, @expiration } if worker.is_a? Worker
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,31 @@
1
+ module Sidekiq::Status::Storage
2
+ RESERVED_FIELDS=%w(status stop update_time).freeze
3
+
4
+ protected
5
+
6
+ # Stores multiple values into a job's status hash,
7
+ # sets last update time
8
+ # @param [String] id job id
9
+ # @param [Hash] status_updates updated values
10
+ # @return [String] Redis operation status code
11
+ def store_for_id(id, status_updates)
12
+ Sidekiq.redis do |conn|
13
+ answers = conn.multi do
14
+ conn.hmset id, 'update_time', Time.now.to_i, *(status_updates.to_a.flatten)
15
+ conn.expire id, Sidekiq::Status::DEFAULT_EXPIRY
16
+ conn.publish "status_updates", id
17
+ end
18
+ answers[0]
19
+ end
20
+ end
21
+
22
+ # Gets a single valued from job status hash
23
+ # @param [String] id job id
24
+ # @param [String] Symbol field fetched field name
25
+ # @return [String] Redis operation status code
26
+ def read_field_for_id(uuid, field)
27
+ Sidekiq.redis do |conn|
28
+ conn.hmget(uuid, field)[0]
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,5 @@
1
+ module Sidekiq
2
+ module Status
3
+ VERSION = "0.1.0"
4
+ end
5
+ end
@@ -0,0 +1,47 @@
1
+ require "active_support"
2
+
3
+ module Sidekiq::Status::Worker
4
+ include Sidekiq::Status::Storage
5
+ extend ActiveSupport::Concern
6
+
7
+ # Adding ID generation to .perform_async
8
+ module ClassMethods
9
+ # :nodoc:
10
+ def self.extended(base)
11
+ class << base
12
+ alias_method_chain :perform_async, :uuid
13
+ end
14
+ end
15
+
16
+ # Add an id to job arguments
17
+ def perform_async_with_uuid(*args)
18
+ id = SecureRandom.uuid
19
+ args.unshift id
20
+ perform_async_without_uuid(*args)
21
+ id
22
+ end
23
+ end
24
+
25
+ class Stopped < StandardError
26
+ end
27
+
28
+ attr_reader :id
29
+
30
+ # Worker id initialization
31
+ # @param [String] id id generated on client-side
32
+ # @raise [RuntimeError] raised in case of second id initialization attempt
33
+ # @return [String] id
34
+ def id=(id)
35
+ raise RuntimeError("Worker ID is already set : #@id") if @id
36
+ @id=id
37
+ end
38
+
39
+ # Stores multiple values into a job's status hash,
40
+ # sets last update time
41
+ # @param [Hash] status_updates updated values
42
+ # @return [String] Redis operation status code
43
+ def store(hash)
44
+ store_for_id(@id, hash)
45
+ end
46
+
47
+ end
@@ -0,0 +1,20 @@
1
+ require "sidekiq-status/version"
2
+ require 'sidekiq-status/storage'
3
+ require 'sidekiq-status/worker'
4
+ require 'sidekiq-status/client_middleware'
5
+ require 'sidekiq-status/server_middleware'
6
+
7
+ module Sidekiq
8
+ module Status
9
+ extend Storage
10
+ DEFAULT_EXPIRY = 60 * 30
11
+ UUID_REGEXP = /[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}/
12
+
13
+ # Job status by id
14
+ # @param [String] id job id returned by async_perform
15
+ # @return [String] job status, possible values: "queued" , "working" , "complete"
16
+ def self.get(id)
17
+ read_field_for_id(id, :status)
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,19 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/sidekiq-status/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ['Evgeniy Tsvigun']
6
+ gem.email = ['utgarda@gmail.com']
7
+ gem.summary = 'An extension to the sidekiq message processing to track your jobs'
8
+ gem.homepage = 'http://github.com/utgarda/sidekiq-status'
9
+ gem.license = 'MIT'
10
+
11
+ gem.files = `git ls-files`.split($\)
12
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
13
+ gem.name = 'sidekiq-status'
14
+ gem.require_paths = ['lib']
15
+ gem.version = Sidekiq::Status::VERSION
16
+
17
+ gem.add_dependency 'sidekiq', '~> 2.2'
18
+ gem.add_development_dependency 'rspec'
19
+ end
@@ -0,0 +1,25 @@
1
+ require 'spec_helper'
2
+
3
+ describe Sidekiq::Status::ClientMiddleware do
4
+
5
+ let!(:redis) { Sidekiq.redis { |conn| conn } }
6
+ let!(:job_id) { SecureRandom.uuid }
7
+
8
+ # Clean Redis before each test
9
+ before { redis.flushall }
10
+
11
+ describe "#call" do
12
+ it "sets queued status" do
13
+ SecureRandom.should_receive(:uuid).once.and_return(job_id)
14
+ StubJob.perform_async(:arg1 => 'val1').should == job_id
15
+ redis.hget(job_id, :status).should == 'queued'
16
+ end
17
+
18
+ it "sets status hash ttl" do
19
+ SecureRandom.should_receive(:uuid).once.and_return(job_id)
20
+ StubJob.perform_async(:arg1 => 'val1').should == job_id
21
+ (1..Sidekiq::Status::DEFAULT_EXPIRY).should cover redis.ttl(job_id)
22
+ end
23
+
24
+ end
25
+ end
@@ -0,0 +1,43 @@
1
+ require 'spec_helper'
2
+
3
+ describe Sidekiq::Status::ServerMiddleware do
4
+
5
+ let!(:redis) { Sidekiq.redis { |conn| conn } }
6
+ let!(:job_id) { SecureRandom.uuid }
7
+
8
+ # Clean Redis before each test
9
+ # Seems like flushall has no effect on recently published messages,
10
+ # so we should wait till they expire
11
+ before { redis.flushall; sleep 0.1 }
12
+
13
+ describe "#call" do
14
+ it "sets working/complete status" do
15
+ thread = confirmations_thread 4, "status_updates", "job_messages_#{job_id}"
16
+ SecureRandom.should_receive(:uuid).once.and_return(job_id)
17
+ start_server do
18
+ ConfirmationJob.perform_async(:arg1 => 'val1').should == job_id
19
+ thread.value.should == [job_id, job_id,
20
+ "while in #perform, status = working",
21
+ job_id]
22
+ end
23
+ redis.hget(job_id, :status).should == 'complete'
24
+ end
25
+
26
+ it "sets failed status" do
27
+ SecureRandom.should_receive(:uuid).once.and_return(job_id)
28
+ start_server do
29
+ capture_status_updates(3) {
30
+ FailingJob.perform_async.should == job_id
31
+ }.should == [job_id]*3
32
+ end
33
+ redis.hget(job_id, :status).should == 'failed'
34
+ end
35
+
36
+ it "sets status hash ttl" do
37
+ SecureRandom.should_receive(:uuid).once.and_return(job_id)
38
+ StubJob.perform_async(:arg1 => 'val1').should == job_id
39
+ (1..Sidekiq::Status::DEFAULT_EXPIRY).should cover redis.ttl(job_id)
40
+ end
41
+
42
+ end
43
+ end
@@ -0,0 +1,13 @@
1
+ require 'spec_helper'
2
+
3
+ describe Sidekiq::Status::Worker do
4
+
5
+ let!(:job_id) { SecureRandom.uuid }
6
+
7
+ describe ".perform_async" do
8
+ it "generates and returns job id" do
9
+ SecureRandom.should_receive(:uuid).once.and_return(job_id)
10
+ StubJob.perform_async().should == job_id
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,55 @@
1
+ require 'spec_helper'
2
+
3
+ describe Sidekiq::Status do
4
+
5
+ let!(:redis) { Sidekiq.redis { |conn| conn } }
6
+ let!(:job_id) { SecureRandom.uuid }
7
+ let!(:job_id_1) { SecureRandom.uuid }
8
+
9
+ # Clean Redis before each test
10
+ # Seems like flushall has no effect on recently published messages,
11
+ # so we should wait till they expire
12
+ before { redis.flushall; sleep 0.1 }
13
+
14
+ describe ".get" do
15
+ it "gets job status by id" do
16
+ SecureRandom.should_receive(:uuid).once.and_return(job_id)
17
+
18
+ start_server do
19
+ capture_status_updates(2) {
20
+ LongJob.perform_async(1).should == job_id
21
+ }.should == [job_id]*2
22
+ Sidekiq::Status.get(job_id).should == "working"
23
+ end
24
+ Sidekiq::Status.get(job_id).should == 'complete'
25
+ end
26
+ end
27
+
28
+ context "keeps normal Sidekiq functionality" do
29
+ it "does jobs with and without status processing" do
30
+ SecureRandom.should_receive(:uuid).twice.and_return(job_id, job_id_1)
31
+ start_server do
32
+ capture_status_updates(6) {
33
+ StubJob.perform_async.should == job_id
34
+ NoStatusConfirmationJob.perform_async(1)
35
+ StubJob.perform_async.should == job_id_1
36
+ NoStatusConfirmationJob.perform_async(2)
37
+ }.should =~ [job_id, job_id_1] * 3
38
+ end
39
+ redis.mget('NoStatusConfirmationJob_1', 'NoStatusConfirmationJob_2').should == %w(done)*2
40
+ Sidekiq::Status.get(job_id).should == 'complete'
41
+ Sidekiq::Status.get(job_id_1).should == 'complete'
42
+ end
43
+
44
+ it "retries failed jobs" do
45
+ SecureRandom.should_receive(:uuid).once.and_return(job_id)
46
+ start_server do
47
+ capture_status_updates(5) {
48
+ RetriedJob.perform_async().should == job_id
49
+ }.should == [job_id] * 5
50
+ end
51
+ Sidekiq::Status.get(job_id).should == 'complete'
52
+ end
53
+ end
54
+
55
+ end
@@ -0,0 +1,66 @@
1
+ require "rspec"
2
+
3
+ require 'sidekiq'
4
+ require 'sidekiq-status'
5
+
6
+
7
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
8
+
9
+ Sidekiq.configure_client do |config|
10
+ config.client_middleware do |chain|
11
+ chain.add Sidekiq::Status::ClientMiddleware
12
+ end
13
+ end
14
+
15
+ def confirmations_thread(messages_limit, *channels)
16
+ parent = Thread.current
17
+ thread = Thread.new {
18
+ confirmations = []
19
+ Sidekiq.redis do |conn|
20
+ conn.subscribe *channels do |on|
21
+ on.subscribe do |ch, subscriptions|
22
+ if subscriptions == channels.size
23
+ sleep 0.1 while parent.status != "sleep"
24
+ parent.run
25
+ end
26
+ end
27
+ on.message do |ch, msg|
28
+ confirmations << msg
29
+ conn.unsubscribe if confirmations.length >= messages_limit
30
+ end
31
+ end
32
+ end
33
+ confirmations
34
+ }
35
+ Thread.stop
36
+ yield if block_given?
37
+ thread
38
+ end
39
+
40
+ def capture_status_updates(n, &block)
41
+ confirmations_thread(n, "status_updates", &block).value
42
+ end
43
+
44
+ def start_server()
45
+ pid = Process.fork do
46
+ $stdout.reopen File::NULL, 'w'
47
+ $stderr.reopen File::NULL, 'w'
48
+ require 'sidekiq/cli'
49
+ Sidekiq.options[:queues] << 'default'
50
+ Sidekiq.configure_server do |config|
51
+ config.redis = {:url => 'redis://localhost:6379'}
52
+ config.server_middleware do |chain|
53
+ chain.add Sidekiq::Status::ServerMiddleware
54
+ end
55
+ end
56
+ Sidekiq::CLI.instance.run
57
+ end
58
+
59
+ yield
60
+
61
+ sleep 0.1
62
+ Process.kill 'TERM', pid
63
+ Timeout::timeout(10) { Process.wait pid } rescue Timeout::Error
64
+ ensure
65
+ Process.kill 'KILL', pid rescue "OK" # it's OK if the process is gone already
66
+ end
@@ -0,0 +1,52 @@
1
+ class StubJob
2
+ include Sidekiq::Worker
3
+ include Sidekiq::Status::Worker
4
+
5
+ sidekiq_options 'retry' => 'false'
6
+
7
+ def perform(*args)
8
+ end
9
+ end
10
+
11
+ class LongJob < StubJob
12
+ def perform(*args)
13
+ sleep args[0] || 1
14
+ end
15
+ end
16
+
17
+ class ConfirmationJob < StubJob
18
+ def perform(*args)
19
+ Sidekiq.redis do |conn|
20
+ conn.publish "job_messages_#{id}", "while in #perform, status = #{conn.hget id, :status}"
21
+ end
22
+ end
23
+ end
24
+
25
+ class NoStatusConfirmationJob
26
+ include Sidekiq::Worker
27
+ def perform(id)
28
+ Sidekiq.redis do |conn|
29
+ conn.set "NoStatusConfirmationJob_#{id}", "done"
30
+ end
31
+
32
+ end
33
+ end
34
+
35
+ class FailingJob < StubJob
36
+ def perform
37
+ raise StandardError
38
+ end
39
+ end
40
+
41
+ class RetriedJob < StubJob
42
+ sidekiq_options 'retry' => 'true'
43
+ def perform()
44
+ Sidekiq.redis do |conn|
45
+ key = "RetriedJob_#{id}"
46
+ unless conn.exists key
47
+ conn.set key, 'tried'
48
+ raise StandardError
49
+ end
50
+ end
51
+ end
52
+ end
metadata ADDED
@@ -0,0 +1,103 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sidekiq-status
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Evgeniy Tsvigun
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-08-19 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: sidekiq
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: '2.2'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: '2.2'
30
+ - !ruby/object:Gem::Dependency
31
+ name: rspec
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ description:
47
+ email:
48
+ - utgarda@gmail.com
49
+ executables: []
50
+ extensions: []
51
+ extra_rdoc_files: []
52
+ files:
53
+ - .gitignore
54
+ - .rspec
55
+ - LICENSE
56
+ - README.md
57
+ - Rakefile
58
+ - lib/sidekiq-status.rb
59
+ - lib/sidekiq-status/client_middleware.rb
60
+ - lib/sidekiq-status/server_middleware.rb
61
+ - lib/sidekiq-status/storage.rb
62
+ - lib/sidekiq-status/version.rb
63
+ - lib/sidekiq-status/worker.rb
64
+ - sidekiq-status.gemspec
65
+ - spec/lib/sidekiq-status/client_middleware_spec.rb
66
+ - spec/lib/sidekiq-status/server_middleware_spec.rb
67
+ - spec/lib/sidekiq-status/worker_spec.rb
68
+ - spec/lib/sidekiq-status_spec.rb
69
+ - spec/spec_helper.rb
70
+ - spec/support/test_jobs.rb
71
+ homepage: http://github.com/utgarda/sidekiq-status
72
+ licenses:
73
+ - MIT
74
+ post_install_message:
75
+ rdoc_options: []
76
+ require_paths:
77
+ - lib
78
+ required_ruby_version: !ruby/object:Gem::Requirement
79
+ none: false
80
+ requirements:
81
+ - - ! '>='
82
+ - !ruby/object:Gem::Version
83
+ version: '0'
84
+ required_rubygems_version: !ruby/object:Gem::Requirement
85
+ none: false
86
+ requirements:
87
+ - - ! '>='
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ requirements: []
91
+ rubyforge_project:
92
+ rubygems_version: 1.8.23
93
+ signing_key:
94
+ specification_version: 3
95
+ summary: An extension to the sidekiq message processing to track your jobs
96
+ test_files:
97
+ - spec/lib/sidekiq-status/client_middleware_spec.rb
98
+ - spec/lib/sidekiq-status/server_middleware_spec.rb
99
+ - spec/lib/sidekiq-status/worker_spec.rb
100
+ - spec/lib/sidekiq-status_spec.rb
101
+ - spec/spec_helper.rb
102
+ - spec/support/test_jobs.rb
103
+ has_rdoc: