sidekiq-cron 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.
- data/.document +5 -0
- data/.travis.yml +18 -0
- data/Changes.md +8 -0
- data/Gemfile +30 -0
- data/LICENSE.txt +20 -0
- data/README.md +152 -0
- data/Rakefile +61 -0
- data/VERSION +1 -0
- data/config.ru +14 -0
- data/examples/web-cron-ui.png +0 -0
- data/lib/sidekiq-cron.rb +4 -0
- data/lib/sidekiq/cron.rb +29 -0
- data/lib/sidekiq/cron/job.rb +397 -0
- data/lib/sidekiq/cron/locales/en.yml +7 -0
- data/lib/sidekiq/cron/poller.rb +65 -0
- data/lib/sidekiq/cron/views/cron.slim +47 -0
- data/lib/sidekiq/cron/web_extension.rb +75 -0
- data/lib/sidekiq/launcher.rb +46 -0
- data/sidekiq-cron.gemspec +104 -0
- data/test/test_helper.rb +67 -0
- data/test/unit/job_test.rb +529 -0
- data/test/unit/poller_test.rb +157 -0
- data/test/unit/web_extesion_test.rb +92 -0
- metadata +313 -0
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'sidekiq'
|
2
|
+
require 'sidekiq/util'
|
3
|
+
require 'sidekiq/actor'
|
4
|
+
require 'sidekiq/cron'
|
5
|
+
|
6
|
+
module Sidekiq
|
7
|
+
module Cron
|
8
|
+
|
9
|
+
POLL_INTERVAL = 10
|
10
|
+
|
11
|
+
##
|
12
|
+
# The Poller checks Redis every N seconds for sheduled cron jobs
|
13
|
+
class Poller
|
14
|
+
include Util
|
15
|
+
include Actor
|
16
|
+
|
17
|
+
def poll(first_time=false)
|
18
|
+
watchdog('scheduling cron poller thread died!') do
|
19
|
+
add_jitter if first_time
|
20
|
+
|
21
|
+
begin
|
22
|
+
time_now = Time.now
|
23
|
+
|
24
|
+
#go through all jobs
|
25
|
+
Sidekiq::Cron::Job.all.each do |job|
|
26
|
+
#test if job should be enequed
|
27
|
+
# if yes add job to queue
|
28
|
+
begin
|
29
|
+
job.test_and_enque_for_time! time_now if job && job.valid?
|
30
|
+
rescue => ex
|
31
|
+
#problem somewhere in one job
|
32
|
+
logger.error "CRON JOB: #{ex.message}"
|
33
|
+
logger.error "CRON JOB: #{ex.backtrace.first}"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
rescue => ex
|
38
|
+
# Most likely a problem with redis networking.
|
39
|
+
# Punt and try again at the next interval
|
40
|
+
logger.error ex.message
|
41
|
+
logger.error ex.backtrace.first
|
42
|
+
end
|
43
|
+
|
44
|
+
after(poll_interval) { poll }
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def poll_interval
|
51
|
+
Sidekiq.options[:poll_interval] || POLL_INTERVAL
|
52
|
+
end
|
53
|
+
|
54
|
+
def add_jitter
|
55
|
+
begin
|
56
|
+
sleep(poll_interval * rand)
|
57
|
+
rescue Celluloid::Task::TerminatedError
|
58
|
+
# Hit Ctrl-C when Sidekiq is finished booting and we have a chance
|
59
|
+
# to get here.
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
header.row
|
2
|
+
.span5
|
3
|
+
h3 = t('CronJobs')
|
4
|
+
- if @cron_jobs.size > 0
|
5
|
+
|
6
|
+
table class="table table-hover table-bordered table-striped"
|
7
|
+
thead
|
8
|
+
th = t('Status')
|
9
|
+
th = t('Name')
|
10
|
+
th = t('Cron')
|
11
|
+
th = t('Last enque')
|
12
|
+
th = t('Arguments')
|
13
|
+
th width="253px"
|
14
|
+
= t('Actions')
|
15
|
+
|
16
|
+
|
17
|
+
- @cron_jobs.sort{|a,b| a.sort_name <=> b.sort_name }.each_with_index do |job, index|
|
18
|
+
- style = "#{job.status == 'disabled' ? "background: #ecc": ""}"
|
19
|
+
tr
|
20
|
+
td[style="#{style}"]= job.status
|
21
|
+
td[style="#{style}"]= job.name
|
22
|
+
td[style="#{style}"]
|
23
|
+
b == job.cron.gsub(" ", " ")
|
24
|
+
td[style="#{style}"]== relative_time(job.last_run_time)
|
25
|
+
td[style="#{style}"]
|
26
|
+
- if job.message and job.message.to_s.size > 100
|
27
|
+
button data-toggle="collapse" data-target=".worker_#{index}" class="btn btn-mini" = t('ShowAll')
|
28
|
+
.toggle[class="worker_#{index}" style="display: inline;"]= job.message[0..100] + "... "
|
29
|
+
.toggle[class="worker_#{index}" style="display: none;"]= job.message
|
30
|
+
- else
|
31
|
+
= job.message
|
32
|
+
td[style="#{style}"]
|
33
|
+
-if job.status == 'enabled'
|
34
|
+
form action="#{root_path}cron/#{job.name}/enque" method="post"
|
35
|
+
input.btn.btn-small.pull-left type="submit" name="enque" value="#{t('EnqueueNow')}"
|
36
|
+
form action="#{root_path}cron/#{job.name}/disable" method="post"
|
37
|
+
input.btn.btn-small.pull-left type="submit" name="disable" value="#{t('Disable')}"
|
38
|
+
-else
|
39
|
+
form action="#{root_path}cron/#{job.name}/enque" method="post"
|
40
|
+
input.btn.btn-small.pull-left type="submit" name="enque" value="#{t('EnqueueNow')}"
|
41
|
+
form action="#{root_path}cron/#{job.name}/enable" method="post"
|
42
|
+
input.btn.btn-small.pull-left type="submit" name="enable" value="#{t('Enable')}"
|
43
|
+
form action="#{root_path}cron/#{job.name}/delete" method="post"
|
44
|
+
input.btn.btn-danger.btn-small type="submit" name="delete" value="#{t('Delete')}" data-confirm="#{t('AreYouSureDeleteCronJob', :job => job.name)}"
|
45
|
+
|
46
|
+
- else
|
47
|
+
.alert.alert-success = t('NoCronJobsFound')
|
@@ -0,0 +1,75 @@
|
|
1
|
+
module Sidekiq
|
2
|
+
module Cron
|
3
|
+
module WebExtension
|
4
|
+
|
5
|
+
def self.registered(app)
|
6
|
+
|
7
|
+
#very bad way of loading locales for cron jobs
|
8
|
+
#should be rewritten
|
9
|
+
app.helpers do
|
10
|
+
|
11
|
+
alias_method :old_strings, :strings
|
12
|
+
|
13
|
+
def strings
|
14
|
+
#only on first load!
|
15
|
+
unless @strings
|
16
|
+
#load all locales from Sidekiq
|
17
|
+
old_strings
|
18
|
+
|
19
|
+
Dir["#{File.join(File.expand_path("..", __FILE__), "locales")}/*.yml"].each do |file|
|
20
|
+
YAML.load(File.open(file)).each do |locale, translations|
|
21
|
+
translations.each do |key, value|
|
22
|
+
@strings[locale][key] = value
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
@strings
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
#index page of cron jobs
|
32
|
+
app.get '/cron' do
|
33
|
+
view_path = File.join(File.expand_path("..", __FILE__), "views")
|
34
|
+
|
35
|
+
@cron_jobs = Sidekiq::Cron::Job.all
|
36
|
+
|
37
|
+
render(:slim, File.read(File.join(view_path, "cron.slim")))
|
38
|
+
end
|
39
|
+
|
40
|
+
#enque cron job
|
41
|
+
app.post '/cron/:name/enque' do |name|
|
42
|
+
if job = Sidekiq::Cron::Job.find(name)
|
43
|
+
job.enque!
|
44
|
+
end
|
45
|
+
redirect "#{root_path}cron"
|
46
|
+
end
|
47
|
+
|
48
|
+
#delete schedule
|
49
|
+
app.post '/cron/:name/delete' do |name|
|
50
|
+
if job = Sidekiq::Cron::Job.find(name)
|
51
|
+
job.destroy
|
52
|
+
end
|
53
|
+
redirect "#{root_path}cron"
|
54
|
+
end
|
55
|
+
|
56
|
+
#enable job
|
57
|
+
app.post '/cron/:name/enable' do |name|
|
58
|
+
if job = Sidekiq::Cron::Job.find(name)
|
59
|
+
job.enable!
|
60
|
+
end
|
61
|
+
redirect "#{root_path}cron"
|
62
|
+
end
|
63
|
+
|
64
|
+
#disable job
|
65
|
+
app.post '/cron/:name/disable' do |name|
|
66
|
+
if job = Sidekiq::Cron::Job.find(name)
|
67
|
+
job.disable!
|
68
|
+
end
|
69
|
+
redirect "#{root_path}cron"
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'sidekiq/cron/poller'
|
2
|
+
|
3
|
+
|
4
|
+
# For Cron we need to add some methods to Launcher
|
5
|
+
# so look at the code bellow.
|
6
|
+
#
|
7
|
+
# we are creating new cron poller instance and
|
8
|
+
# adding start and stop commands to launcher
|
9
|
+
module Sidekiq
|
10
|
+
class Launcher
|
11
|
+
|
12
|
+
#Add cron poller to launcher
|
13
|
+
attr_reader :cron_poller
|
14
|
+
|
15
|
+
|
16
|
+
#remember old initialize
|
17
|
+
alias_method :old_initialize, :initialize
|
18
|
+
|
19
|
+
#add cron poller and execute normal initialize of Sidekiq launcher
|
20
|
+
def initialize(options)
|
21
|
+
@cron_poller = Sidekiq::Cron::Poller.new
|
22
|
+
old_initialize options
|
23
|
+
end
|
24
|
+
|
25
|
+
|
26
|
+
#remember old run
|
27
|
+
alias_method :old_run, :run
|
28
|
+
|
29
|
+
#execute normal run of launcher and run cron poller
|
30
|
+
def run
|
31
|
+
old_run
|
32
|
+
cron_poller.async.poll(true)
|
33
|
+
end
|
34
|
+
|
35
|
+
|
36
|
+
#remember old stop
|
37
|
+
alias_method :old_stop, :stop
|
38
|
+
|
39
|
+
#execute normal stop of launcher and stop cron poller
|
40
|
+
def stop
|
41
|
+
cron_poller.async.terminate if poller.alive?
|
42
|
+
old_stop
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = "sidekiq-cron"
|
8
|
+
s.version = "0.1.0"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Ondrej Bartas"]
|
12
|
+
s.date = "2013-08-25"
|
13
|
+
s.description = "Enables to set jobs to be run in specified time (using CRON notation)"
|
14
|
+
s.email = "ondrej@bartas.cz"
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"LICENSE.txt",
|
17
|
+
"README.md"
|
18
|
+
]
|
19
|
+
s.files = [
|
20
|
+
".document",
|
21
|
+
".travis.yml",
|
22
|
+
"Changes.md",
|
23
|
+
"Gemfile",
|
24
|
+
"LICENSE.txt",
|
25
|
+
"README.md",
|
26
|
+
"Rakefile",
|
27
|
+
"VERSION",
|
28
|
+
"config.ru",
|
29
|
+
"examples/web-cron-ui.png",
|
30
|
+
"lib/sidekiq-cron.rb",
|
31
|
+
"lib/sidekiq/cron.rb",
|
32
|
+
"lib/sidekiq/cron/job.rb",
|
33
|
+
"lib/sidekiq/cron/locales/en.yml",
|
34
|
+
"lib/sidekiq/cron/poller.rb",
|
35
|
+
"lib/sidekiq/cron/views/cron.slim",
|
36
|
+
"lib/sidekiq/cron/web_extension.rb",
|
37
|
+
"lib/sidekiq/launcher.rb",
|
38
|
+
"sidekiq-cron.gemspec",
|
39
|
+
"test/test_helper.rb",
|
40
|
+
"test/unit/job_test.rb",
|
41
|
+
"test/unit/poller_test.rb",
|
42
|
+
"test/unit/web_extesion_test.rb"
|
43
|
+
]
|
44
|
+
s.homepage = "http://github.com/ondrejbartas/sidekiq-cron"
|
45
|
+
s.licenses = ["MIT"]
|
46
|
+
s.require_paths = ["lib"]
|
47
|
+
s.rubygems_version = "1.8.25"
|
48
|
+
s.summary = "Sidekiq Cron helps to add repeated scheduled jobs"
|
49
|
+
|
50
|
+
if s.respond_to? :specification_version then
|
51
|
+
s.specification_version = 3
|
52
|
+
|
53
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
54
|
+
s.add_runtime_dependency(%q<sidekiq>, [">= 2.13.1"])
|
55
|
+
s.add_runtime_dependency(%q<parse-cron>, [">= 0.1.2"])
|
56
|
+
s.add_development_dependency(%q<bundler>, [">= 0"])
|
57
|
+
s.add_development_dependency(%q<simplecov>, [">= 0"])
|
58
|
+
s.add_development_dependency(%q<shoulda-context>, [">= 0"])
|
59
|
+
s.add_development_dependency(%q<turn>, [">= 0"])
|
60
|
+
s.add_development_dependency(%q<rack>, [">= 0"])
|
61
|
+
s.add_development_dependency(%q<rack-test>, [">= 0"])
|
62
|
+
s.add_development_dependency(%q<jeweler>, ["~> 1.8.3"])
|
63
|
+
s.add_development_dependency(%q<sdoc>, [">= 0"])
|
64
|
+
s.add_development_dependency(%q<slim>, [">= 0"])
|
65
|
+
s.add_development_dependency(%q<sinatra>, [">= 0"])
|
66
|
+
s.add_development_dependency(%q<mocha>, [">= 0"])
|
67
|
+
s.add_development_dependency(%q<coveralls>, [">= 0"])
|
68
|
+
s.add_development_dependency(%q<shotgun>, [">= 0"])
|
69
|
+
else
|
70
|
+
s.add_dependency(%q<sidekiq>, [">= 2.13.1"])
|
71
|
+
s.add_dependency(%q<parse-cron>, [">= 0.1.2"])
|
72
|
+
s.add_dependency(%q<bundler>, [">= 0"])
|
73
|
+
s.add_dependency(%q<simplecov>, [">= 0"])
|
74
|
+
s.add_dependency(%q<shoulda-context>, [">= 0"])
|
75
|
+
s.add_dependency(%q<turn>, [">= 0"])
|
76
|
+
s.add_dependency(%q<rack>, [">= 0"])
|
77
|
+
s.add_dependency(%q<rack-test>, [">= 0"])
|
78
|
+
s.add_dependency(%q<jeweler>, ["~> 1.8.3"])
|
79
|
+
s.add_dependency(%q<sdoc>, [">= 0"])
|
80
|
+
s.add_dependency(%q<slim>, [">= 0"])
|
81
|
+
s.add_dependency(%q<sinatra>, [">= 0"])
|
82
|
+
s.add_dependency(%q<mocha>, [">= 0"])
|
83
|
+
s.add_dependency(%q<coveralls>, [">= 0"])
|
84
|
+
s.add_dependency(%q<shotgun>, [">= 0"])
|
85
|
+
end
|
86
|
+
else
|
87
|
+
s.add_dependency(%q<sidekiq>, [">= 2.13.1"])
|
88
|
+
s.add_dependency(%q<parse-cron>, [">= 0.1.2"])
|
89
|
+
s.add_dependency(%q<bundler>, [">= 0"])
|
90
|
+
s.add_dependency(%q<simplecov>, [">= 0"])
|
91
|
+
s.add_dependency(%q<shoulda-context>, [">= 0"])
|
92
|
+
s.add_dependency(%q<turn>, [">= 0"])
|
93
|
+
s.add_dependency(%q<rack>, [">= 0"])
|
94
|
+
s.add_dependency(%q<rack-test>, [">= 0"])
|
95
|
+
s.add_dependency(%q<jeweler>, ["~> 1.8.3"])
|
96
|
+
s.add_dependency(%q<sdoc>, [">= 0"])
|
97
|
+
s.add_dependency(%q<slim>, [">= 0"])
|
98
|
+
s.add_dependency(%q<sinatra>, [">= 0"])
|
99
|
+
s.add_dependency(%q<mocha>, [">= 0"])
|
100
|
+
s.add_dependency(%q<coveralls>, [">= 0"])
|
101
|
+
s.add_dependency(%q<shotgun>, [">= 0"])
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler'
|
3
|
+
begin
|
4
|
+
Bundler.setup(:default, :development)
|
5
|
+
rescue Bundler::BundlerError => e
|
6
|
+
$stderr.puts e.message
|
7
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
8
|
+
exit e.status_code
|
9
|
+
end
|
10
|
+
|
11
|
+
require 'simplecov'
|
12
|
+
SimpleCov.start do
|
13
|
+
add_filter "/test/"
|
14
|
+
|
15
|
+
add_group 'SidekiqCron', 'lib/'
|
16
|
+
end
|
17
|
+
require 'coveralls'
|
18
|
+
Coveralls.wear!
|
19
|
+
|
20
|
+
require "minitest/autorun"
|
21
|
+
require 'shoulda-context'
|
22
|
+
require 'turn'
|
23
|
+
require "rack/test"
|
24
|
+
require "mocha/setup"
|
25
|
+
|
26
|
+
#SIDEKIQ Require - need to have sidekiq running!
|
27
|
+
require 'celluloid/autostart'
|
28
|
+
require 'sidekiq'
|
29
|
+
require 'sidekiq/util'
|
30
|
+
require 'sidekiq/web'
|
31
|
+
|
32
|
+
Sidekiq.logger.level = Logger::ERROR
|
33
|
+
|
34
|
+
require 'sidekiq/redis_connection'
|
35
|
+
redis_url = ENV['REDIS_URL'] || 'redis://localhost/15'
|
36
|
+
REDIS = Sidekiq::RedisConnection.create(:url => redis_url, :namespace => 'testy')
|
37
|
+
|
38
|
+
Sidekiq.configure_client do |config|
|
39
|
+
config.redis = { :url => redis_url, :namespace => 'testy' }
|
40
|
+
end
|
41
|
+
|
42
|
+
|
43
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
44
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
45
|
+
require 'sidekiq-cron'
|
46
|
+
|
47
|
+
class Test::Unit::TestCase
|
48
|
+
end
|
49
|
+
|
50
|
+
|
51
|
+
class CronTestClass
|
52
|
+
include Sidekiq::Worker
|
53
|
+
|
54
|
+
def perform args = {}
|
55
|
+
puts "super croned job #{args}"
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
class CronTestClassWithQueue
|
60
|
+
include Sidekiq::Worker
|
61
|
+
sidekiq_options :queue => :super, :retry => false, :backtrace => true
|
62
|
+
|
63
|
+
def perform args = {}
|
64
|
+
puts "super croned job #{args}"
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
@@ -0,0 +1,529 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
require './test/test_helper'
|
3
|
+
|
4
|
+
class CronJobTest < Test::Unit::TestCase
|
5
|
+
context "Cron Job" do
|
6
|
+
|
7
|
+
setup do
|
8
|
+
#clear all previous saved data from redis
|
9
|
+
Sidekiq.redis do |conn|
|
10
|
+
conn.keys("cron_job*").each do |key|
|
11
|
+
conn.del(key)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
#clear all queues
|
16
|
+
Sidekiq::Queue.all.each do |queue|
|
17
|
+
queue.clear
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
should "be initialized" do
|
22
|
+
job = Sidekiq::Cron::Job.new()
|
23
|
+
assert job.is_a?(Sidekiq::Cron::Job)
|
24
|
+
end
|
25
|
+
|
26
|
+
context "class methods" do
|
27
|
+
should "have create method" do
|
28
|
+
assert Sidekiq::Cron::Job.respond_to?(:create)
|
29
|
+
end
|
30
|
+
|
31
|
+
should "have destroy method" do
|
32
|
+
assert Sidekiq::Cron::Job.respond_to?(:destroy)
|
33
|
+
end
|
34
|
+
|
35
|
+
should "have count" do
|
36
|
+
assert Sidekiq::Cron::Job.respond_to?(:count)
|
37
|
+
end
|
38
|
+
|
39
|
+
should "have all" do
|
40
|
+
assert Sidekiq::Cron::Job.respond_to?(:all)
|
41
|
+
end
|
42
|
+
|
43
|
+
should "have find" do
|
44
|
+
assert Sidekiq::Cron::Job.respond_to?(:find)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
context "instance methods" do
|
49
|
+
setup do
|
50
|
+
@job = Sidekiq::Cron::Job.new()
|
51
|
+
end
|
52
|
+
|
53
|
+
should "have save method" do
|
54
|
+
assert @job.respond_to?(:save)
|
55
|
+
end
|
56
|
+
should "have valid? method" do
|
57
|
+
assert @job.respond_to?("valid?".to_sym)
|
58
|
+
end
|
59
|
+
should "have destroy method" do
|
60
|
+
assert @job.respond_to?(:destroy)
|
61
|
+
end
|
62
|
+
|
63
|
+
should 'have sort_name - used for sorting enabled disbaled jobs on frontend' do
|
64
|
+
job = Sidekiq::Cron::Job.new(name: "TestName")
|
65
|
+
assert_equal job.sort_name, "0_testname"
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
context "invalid job" do
|
70
|
+
|
71
|
+
setup do
|
72
|
+
@job = Sidekiq::Cron::Job.new()
|
73
|
+
end
|
74
|
+
|
75
|
+
should "return false on valid? and errors" do
|
76
|
+
refute @job.valid?
|
77
|
+
assert @job.errors.is_a?(Array)
|
78
|
+
|
79
|
+
assert @job.errors.any?{|e| e.include?("name")}, "Should have error for name"
|
80
|
+
assert @job.errors.any?{|e| e.include?("cron")}, "Should have error for cron"
|
81
|
+
assert @job.errors.any?{|e| e.include?("klass")}, "Should have error for klass"
|
82
|
+
end
|
83
|
+
|
84
|
+
should "return false on valid? with invalid cron" do
|
85
|
+
@job.cron = "* s *"
|
86
|
+
refute @job.valid?
|
87
|
+
assert @job.errors.is_a?(Array)
|
88
|
+
assert @job.errors.any?{|e| e.include?("cron")}, "Should have error for cron"
|
89
|
+
end
|
90
|
+
|
91
|
+
should "return false on save" do
|
92
|
+
refute @job.save
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
context "new" do
|
97
|
+
setup do
|
98
|
+
@args = {
|
99
|
+
name: "Test",
|
100
|
+
cron: "* * * * *"
|
101
|
+
}
|
102
|
+
@job = Sidekiq::Cron::Job.new(@args)
|
103
|
+
end
|
104
|
+
|
105
|
+
should "have all setted attributes" do
|
106
|
+
@args.each do |key, value|
|
107
|
+
assert_equal @job.send(key), value, "New job should have #{key} with value #{value} but it has: #{@job.send(key)}"
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
should "have to_hash method" do
|
112
|
+
[:name,:klass,:cron,:args,:message,:status, :last_run_time].each do |key|
|
113
|
+
assert @job.to_hash.has_key?(key), "to_hash must have key: #{key}"
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
context "new with different class inputs" do
|
119
|
+
should "be initialized by 'klass' and Class" do
|
120
|
+
assert_nothing_raised do
|
121
|
+
Sidekiq::Cron::Job.new('klass' => CronTestClass)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
should "be initialized by 'klass' and string Class" do
|
126
|
+
assert_nothing_raised do
|
127
|
+
Sidekiq::Cron::Job.new('klass' => 'CronTestClass')
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
should "be initialized by 'class' and string Class" do
|
132
|
+
assert_nothing_raised do
|
133
|
+
Sidekiq::Cron::Job.new('class' => 'CronTestClass')
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
should "be initialized by 'class' and Class" do
|
138
|
+
assert_nothing_raised do
|
139
|
+
Sidekiq::Cron::Job.new('class' => CronTestClass)
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
context "new should find klass specific settings (queue, retry ...)" do
|
145
|
+
should "nothing raise on unknown klass" do
|
146
|
+
assert_nothing_raised do
|
147
|
+
job = Sidekiq::Cron::Job.new('klass' => 'UnknownCronClass')
|
148
|
+
assert_equal job.message, {"class"=>"UnknownCronClass", "args"=>[], "queue"=>"default"}
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
should "be initialized with default attributes" do
|
153
|
+
assert_nothing_raised do
|
154
|
+
job = Sidekiq::Cron::Job.new('klass' => 'CronTestClass')
|
155
|
+
|
156
|
+
assert_equal job.message, {"retry"=>true, "queue"=>"default", "class"=>"CronTestClass", "args"=>[]}
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
should "be initialized with class specified attributes" do
|
161
|
+
assert_nothing_raised do
|
162
|
+
job = Sidekiq::Cron::Job.new('class' => 'CronTestClassWithQueue')
|
163
|
+
assert_equal job.message, {"retry"=>false,
|
164
|
+
"queue"=>:super,
|
165
|
+
"backtrace"=>true,
|
166
|
+
"class"=>"CronTestClassWithQueue",
|
167
|
+
"args"=>[]}
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
should "be initialized with 'class' and overwrite queue by settings" do
|
172
|
+
assert_nothing_raised do
|
173
|
+
job = Sidekiq::Cron::Job.new('class' => CronTestClassWithQueue, queue: 'my_testing_queue')
|
174
|
+
|
175
|
+
assert_equal job.message, {"retry"=>false,
|
176
|
+
"queue"=>'my_testing_queue',
|
177
|
+
"backtrace"=>true,
|
178
|
+
"class"=>"CronTestClassWithQueue",
|
179
|
+
"args"=>[]}
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
context "cron test" do
|
185
|
+
setup do
|
186
|
+
@job = Sidekiq::Cron::Job.new()
|
187
|
+
end
|
188
|
+
|
189
|
+
should "return previous minute" do
|
190
|
+
@job.cron = "* * * * *"
|
191
|
+
time = Time.now
|
192
|
+
assert_equal @job.last_time(time).strftime("%Y-%m-%d-%H-%M-%S"), time.strftime("%Y-%m-%d-%H-%M-00")
|
193
|
+
end
|
194
|
+
|
195
|
+
should "return previous hour" do
|
196
|
+
@job.cron = "1 * * * *"
|
197
|
+
time = Time.now
|
198
|
+
assert_equal @job.last_time(time).strftime("%Y-%m-%d-%H-%M-%S"), time.strftime("%Y-%m-%d-%H-01-00")
|
199
|
+
end
|
200
|
+
|
201
|
+
should "return previous day" do
|
202
|
+
@job.cron = "1 2 * * *"
|
203
|
+
time = Time.now
|
204
|
+
assert_equal @job.last_time(time).strftime("%Y-%m-%d-%H-%M-%S"), time.strftime("%Y-%m-%d-02-01-00")
|
205
|
+
end
|
206
|
+
|
207
|
+
end
|
208
|
+
|
209
|
+
context "save" do
|
210
|
+
setup do
|
211
|
+
@args = {
|
212
|
+
name: "Test",
|
213
|
+
cron: "* * * * *",
|
214
|
+
klass: "CronTestClass"
|
215
|
+
}
|
216
|
+
@job = Sidekiq::Cron::Job.new(@args)
|
217
|
+
end
|
218
|
+
|
219
|
+
should "be saved" do
|
220
|
+
assert @job.save
|
221
|
+
end
|
222
|
+
|
223
|
+
|
224
|
+
should "be saved and found by name" do
|
225
|
+
assert @job.save, "not saved"
|
226
|
+
assert Sidekiq::Cron::Job.find("Test").is_a?(Sidekiq::Cron::Job)
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
context "nonexisting job" do
|
231
|
+
should "not be found" do
|
232
|
+
assert Sidekiq::Cron::Job.find("nonexisting").nil?, "should return nil"
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
context "disabled/enabled" do
|
237
|
+
setup do
|
238
|
+
@args = {
|
239
|
+
name: "Test",
|
240
|
+
cron: "* * * * *",
|
241
|
+
klass: "CronTestClass"
|
242
|
+
}
|
243
|
+
end
|
244
|
+
|
245
|
+
should "be created and enabled" do
|
246
|
+
Sidekiq::Cron::Job.create(@args)
|
247
|
+
job = Sidekiq::Cron::Job.find(@args)
|
248
|
+
assert_equal job.status, "enabled"
|
249
|
+
end
|
250
|
+
|
251
|
+
should "be created and then enabled and disabled" do
|
252
|
+
Sidekiq::Cron::Job.create(@args)
|
253
|
+
job = Sidekiq::Cron::Job.find(@args)
|
254
|
+
assert_equal job.status, "enabled"
|
255
|
+
|
256
|
+
job.enable!
|
257
|
+
assert_equal job.status, "enabled"
|
258
|
+
|
259
|
+
job.disable!
|
260
|
+
assert_equal job.status, "disabled"
|
261
|
+
end
|
262
|
+
|
263
|
+
should "be created with status disabled" do
|
264
|
+
Sidekiq::Cron::Job.create(@args.merge(status: "disabled"))
|
265
|
+
job = Sidekiq::Cron::Job.find(@args)
|
266
|
+
assert_equal job.status, "disabled"
|
267
|
+
end
|
268
|
+
|
269
|
+
should "be created with status enabled and disable it afterwards" do
|
270
|
+
Sidekiq::Cron::Job.create(@args)
|
271
|
+
job = Sidekiq::Cron::Job.find(@args)
|
272
|
+
assert_equal job.status, "enabled"
|
273
|
+
job.disable!
|
274
|
+
assert_equal job.status, "disabled", "directly after call"
|
275
|
+
job = Sidekiq::Cron::Job.find(@args)
|
276
|
+
assert_equal job.status, "disabled", "after find"
|
277
|
+
end
|
278
|
+
|
279
|
+
should "status shouldn't be rewritten after save without status" do
|
280
|
+
Sidekiq::Cron::Job.create(@args)
|
281
|
+
job = Sidekiq::Cron::Job.find(@args)
|
282
|
+
assert_equal job.status, "enabled"
|
283
|
+
job.disable!
|
284
|
+
assert_equal job.status, "disabled", "directly after call"
|
285
|
+
job = Sidekiq::Cron::Job.find(@args)
|
286
|
+
assert_equal job.status, "disabled", "after find"
|
287
|
+
|
288
|
+
Sidekiq::Cron::Job.create(@args)
|
289
|
+
assert_equal job.status, "disabled", "after second create"
|
290
|
+
job = Sidekiq::Cron::Job.find(@args)
|
291
|
+
assert_equal job.status, "disabled", "after second find"
|
292
|
+
end
|
293
|
+
end
|
294
|
+
|
295
|
+
context "create & find methods" do
|
296
|
+
setup do
|
297
|
+
@args = {
|
298
|
+
name: "Test",
|
299
|
+
cron: "* * * * *",
|
300
|
+
klass: "CronTestClass"
|
301
|
+
}
|
302
|
+
end
|
303
|
+
|
304
|
+
should "create first three jobs" do
|
305
|
+
assert_equal Sidekiq::Cron::Job.count, 0, "Should have 0 jobs"
|
306
|
+
Sidekiq::Cron::Job.create(@args)
|
307
|
+
Sidekiq::Cron::Job.create(@args.merge(name: "Test2"))
|
308
|
+
Sidekiq::Cron::Job.create(@args.merge(name: "Test3"))
|
309
|
+
assert_equal Sidekiq::Cron::Job.count, 3, "Should have 3 jobs"
|
310
|
+
end
|
311
|
+
|
312
|
+
should "create first three jobs - 1 has same name" do
|
313
|
+
assert_equal Sidekiq::Cron::Job.count, 0, "Should have 0 jobs"
|
314
|
+
Sidekiq::Cron::Job.create(@args)
|
315
|
+
Sidekiq::Cron::Job.create(@args.merge(name: "Test2"))
|
316
|
+
Sidekiq::Cron::Job.create(@args.merge(cron: "1 * * * *"))
|
317
|
+
assert_equal Sidekiq::Cron::Job.count, 2, "Should have 2 jobs"
|
318
|
+
end
|
319
|
+
|
320
|
+
should "be found by method all" do
|
321
|
+
Sidekiq::Cron::Job.create(@args)
|
322
|
+
Sidekiq::Cron::Job.create(@args.merge(name: "Test2"))
|
323
|
+
Sidekiq::Cron::Job.create(@args.merge(name: "Test3"))
|
324
|
+
assert_equal Sidekiq::Cron::Job.all.size, 3, "Should have 3 jobs"
|
325
|
+
assert Sidekiq::Cron::Job.all.all?{|j| j.is_a?(Sidekiq::Cron::Job)}, "All returned jobs should be Job class"
|
326
|
+
end
|
327
|
+
|
328
|
+
should "be found by method all - defect in set" do
|
329
|
+
Sidekiq::Cron::Job.create(@args)
|
330
|
+
Sidekiq::Cron::Job.create(@args.merge(name: "Test2"))
|
331
|
+
Sidekiq::Cron::Job.create(@args.merge(name: "Test3"))
|
332
|
+
|
333
|
+
Sidekiq.redis do |conn|
|
334
|
+
conn.sadd Sidekiq::Cron::Job.jobs_key, "some_other_key"
|
335
|
+
end
|
336
|
+
|
337
|
+
assert_equal Sidekiq::Cron::Job.all.size, 3, "All have to return only valid 3 jobs"
|
338
|
+
end
|
339
|
+
|
340
|
+
should "be found by string name" do
|
341
|
+
Sidekiq::Cron::Job.create(@args)
|
342
|
+
assert Sidekiq::Cron::Job.find("Test")
|
343
|
+
end
|
344
|
+
|
345
|
+
should "be found by hash with key name" do
|
346
|
+
Sidekiq::Cron::Job.create(@args)
|
347
|
+
assert Sidekiq::Cron::Job.find(name: "Test"), "symbol keys keys"
|
348
|
+
|
349
|
+
Sidekiq::Cron::Job.create(@args)
|
350
|
+
assert Sidekiq::Cron::Job.find('name' => "Test"), "String keys"
|
351
|
+
end
|
352
|
+
|
353
|
+
end
|
354
|
+
|
355
|
+
context "destroy" do
|
356
|
+
setup do
|
357
|
+
@args = {
|
358
|
+
name: "Test",
|
359
|
+
cron: "* * * * *",
|
360
|
+
klass: "CronTestClass"
|
361
|
+
}
|
362
|
+
end
|
363
|
+
|
364
|
+
should "create and then destroy by hash" do
|
365
|
+
Sidekiq::Cron::Job.create(@args)
|
366
|
+
assert_equal Sidekiq::Cron::Job.all.size, 1, "Should have 1 job"
|
367
|
+
|
368
|
+
assert Sidekiq::Cron::Job.destroy(@args)
|
369
|
+
assert_equal Sidekiq::Cron::Job.all.size, 0, "Should have 0 job after destroy"
|
370
|
+
end
|
371
|
+
|
372
|
+
should "return false on destroying nonexisting" do
|
373
|
+
assert_equal Sidekiq::Cron::Job.all.size, 0, "Should have 0 jobs"
|
374
|
+
refute Sidekiq::Cron::Job.destroy("nonexisting")
|
375
|
+
end
|
376
|
+
|
377
|
+
should "return destroy by string name" do
|
378
|
+
Sidekiq::Cron::Job.create(@args)
|
379
|
+
assert Sidekiq::Cron::Job.destroy("Test")
|
380
|
+
end
|
381
|
+
|
382
|
+
should "return destroy by hash with key name" do
|
383
|
+
Sidekiq::Cron::Job.create(@args)
|
384
|
+
assert Sidekiq::Cron::Job.destroy(name: "Test"), "symbol keys keys"
|
385
|
+
|
386
|
+
Sidekiq::Cron::Job.create(@args)
|
387
|
+
assert Sidekiq::Cron::Job.destroy('name' => "Test"), "String keys"
|
388
|
+
end
|
389
|
+
|
390
|
+
end
|
391
|
+
|
392
|
+
context "test of enque" do
|
393
|
+
setup do
|
394
|
+
@args = {
|
395
|
+
name: "Test",
|
396
|
+
cron: "* * * * *",
|
397
|
+
klass: "CronTestClass"
|
398
|
+
}
|
399
|
+
#first time is allways
|
400
|
+
#after next cron time!
|
401
|
+
@time = Time.now + 120
|
402
|
+
end
|
403
|
+
should "be allways false when status is disabled" do
|
404
|
+
refute Sidekiq::Cron::Job.new(@args.merge(status: 'disabled')).should_enque? @time
|
405
|
+
refute Sidekiq::Cron::Job.new(@args.merge(status: 'disabled')).should_enque? @time - 60
|
406
|
+
refute Sidekiq::Cron::Job.new(@args.merge(status: 'disabled')).should_enque? @time - 120
|
407
|
+
assert_equal Sidekiq::Queue.all.size, 0, "Sidekiq 0 queues"
|
408
|
+
end
|
409
|
+
|
410
|
+
should "be false for same times" do
|
411
|
+
assert Sidekiq::Cron::Job.new(@args).should_enque?(@time), "First time - true"
|
412
|
+
refute Sidekiq::Cron::Job.new(@args).should_enque? @time
|
413
|
+
refute Sidekiq::Cron::Job.new(@args).should_enque? @time
|
414
|
+
end
|
415
|
+
|
416
|
+
should "be false for same times but true for next time" do
|
417
|
+
assert Sidekiq::Cron::Job.new(@args).should_enque?(@time), "First time - true"
|
418
|
+
refute Sidekiq::Cron::Job.new(@args).should_enque? @time
|
419
|
+
assert Sidekiq::Cron::Job.new(@args).should_enque? @time + 135
|
420
|
+
refute Sidekiq::Cron::Job.new(@args).should_enque? @time + 135
|
421
|
+
assert Sidekiq::Cron::Job.new(@args).should_enque? @time + 235
|
422
|
+
refute Sidekiq::Cron::Job.new(@args).should_enque? @time + 235
|
423
|
+
|
424
|
+
#just for check
|
425
|
+
refute Sidekiq::Cron::Job.new(@args).should_enque? @time
|
426
|
+
refute Sidekiq::Cron::Job.new(@args).should_enque? @time + 135
|
427
|
+
refute Sidekiq::Cron::Job.new(@args).should_enque? @time + 235
|
428
|
+
end
|
429
|
+
|
430
|
+
should "remove old enque times + should be enqeued" do
|
431
|
+
assert Sidekiq::Cron::Job.new(@args).test_and_enque_for_time!(@time), "should enqueue"
|
432
|
+
refute Sidekiq::Cron::Job.new(@args).test_and_enque_for_time!(@time), "should not enqueue"
|
433
|
+
Sidekiq.redis do |conn|
|
434
|
+
assert_equal conn.zcard(Sidekiq::Cron::Job.new(@args).send(:job_enqueued_key)), 2, "Should have two enqueued job (first was in save, second in enque)"
|
435
|
+
end
|
436
|
+
assert_equal Sidekiq::Queue.all.first.size, 1, "Sidekiq queue 1 job in queue"
|
437
|
+
|
438
|
+
# 20 hours after
|
439
|
+
assert Sidekiq::Cron::Job.new(@args).test_and_enque_for_time! @time + 1 * 60 * 60
|
440
|
+
refute Sidekiq::Cron::Job.new(@args).test_and_enque_for_time! @time + 1 * 60 * 60
|
441
|
+
|
442
|
+
Sidekiq.redis do |conn|
|
443
|
+
assert_equal conn.zcard(Sidekiq::Cron::Job.new(@args).send(:job_enqueued_key)), 3, "Should have two enqueued job + one from start"
|
444
|
+
end
|
445
|
+
assert_equal Sidekiq::Queue.all.first.size, 2, "Sidekiq queue 2 jobs in queue"
|
446
|
+
|
447
|
+
# 26 hour after
|
448
|
+
assert Sidekiq::Cron::Job.new(@args).test_and_enque_for_time! @time + 26 * 60 * 60
|
449
|
+
refute Sidekiq::Cron::Job.new(@args).test_and_enque_for_time! @time + 26 * 60 * 60
|
450
|
+
|
451
|
+
Sidekiq.redis do |conn|
|
452
|
+
assert_equal conn.zcard(Sidekiq::Cron::Job.new(@args).send(:job_enqueued_key)), 1, "Should have one enqueued job - old jobs should be deleted"
|
453
|
+
end
|
454
|
+
assert_equal Sidekiq::Queue.all.first.size, 3, "Sidekiq queue 3 jobs in queue"
|
455
|
+
end
|
456
|
+
end
|
457
|
+
|
458
|
+
context "load" do
|
459
|
+
|
460
|
+
context "from hash" do
|
461
|
+
setup do
|
462
|
+
@jobs_hash = {
|
463
|
+
'name_of_job' => {
|
464
|
+
'class' => 'MyClass',
|
465
|
+
'cron' => '1 * * * *',
|
466
|
+
'args' => '(OPTIONAL) [Array or Hash]'
|
467
|
+
},
|
468
|
+
'My super iber cool job' => {
|
469
|
+
'class' => 'SecondClass',
|
470
|
+
'cron' => '*/5 * * * *'
|
471
|
+
}
|
472
|
+
}
|
473
|
+
end
|
474
|
+
|
475
|
+
should "create new jobs and update old one with same settings" do
|
476
|
+
assert_equal Sidekiq::Cron::Job.all.size, 0, "Should have 0 jobs before load"
|
477
|
+
out = Sidekiq::Cron::Job.load_from_hash @jobs_hash
|
478
|
+
assert_equal out.size, 0, "should have no errors"
|
479
|
+
assert_equal Sidekiq::Cron::Job.all.size, 2, "Should have 2 jobs after load"
|
480
|
+
end
|
481
|
+
|
482
|
+
should "return errors on loaded jobs" do
|
483
|
+
assert_equal Sidekiq::Cron::Job.all.size, 0, "Should have 0 jobs before load"
|
484
|
+
#set something bag to hash
|
485
|
+
@jobs_hash['name_of_job']['cron'] = "bad cron"
|
486
|
+
out = Sidekiq::Cron::Job.load_from_hash @jobs_hash
|
487
|
+
assert_equal out.size, 1, "should have 1 error"
|
488
|
+
assert_equal out, {"name_of_job"=>["'cron' -> bad cron: Bad Vixie-style specification bad"]}
|
489
|
+
assert_equal Sidekiq::Cron::Job.all.size, 1, "Should have only 1 job after load"
|
490
|
+
end
|
491
|
+
|
492
|
+
should "create new jobs and then destroy them all" do
|
493
|
+
assert_equal Sidekiq::Cron::Job.all.size, 0, "Should have 0 jobs before load"
|
494
|
+
out = Sidekiq::Cron::Job.load_from_hash @jobs_hash
|
495
|
+
assert_equal out.size, 0, "should have no errors"
|
496
|
+
assert_equal Sidekiq::Cron::Job.all.size, 2, "Should have 2 jobs after load"
|
497
|
+
Sidekiq::Cron::Job.destroy_all!
|
498
|
+
assert_equal Sidekiq::Cron::Job.all.size, 0, "Should have 0 jobs after destroy all"
|
499
|
+
end
|
500
|
+
|
501
|
+
end
|
502
|
+
|
503
|
+
context "from array" do
|
504
|
+
setup do
|
505
|
+
@jobs_array = [
|
506
|
+
{
|
507
|
+
'name' => 'name_of_job',
|
508
|
+
'class' => 'MyClass',
|
509
|
+
'cron' => '1 * * * *',
|
510
|
+
'args' => '(OPTIONAL) [Array or Hash]'
|
511
|
+
},
|
512
|
+
{
|
513
|
+
'name' => 'Cool Job for Second Class',
|
514
|
+
'class' => 'SecondClass',
|
515
|
+
'cron' => '*/5 * * * *'
|
516
|
+
}
|
517
|
+
]
|
518
|
+
end
|
519
|
+
|
520
|
+
should "create new jobs and update old one with same settings" do
|
521
|
+
assert_equal Sidekiq::Cron::Job.all.size, 0, "Should have 0 jobs before load"
|
522
|
+
out = Sidekiq::Cron::Job.load_from_array @jobs_array
|
523
|
+
assert_equal out.size, 0, "should have 0 error"
|
524
|
+
assert_equal Sidekiq::Cron::Job.all.size, 2, "Should have 2 jobs after load"
|
525
|
+
end
|
526
|
+
end
|
527
|
+
end
|
528
|
+
end
|
529
|
+
end
|