recurrent 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.autotest +13 -0
- data/.gitignore +7 -0
- data/.rspec +2 -0
- data/Gemfile +3 -0
- data/LICENSE +20 -0
- data/README.markdown +0 -0
- data/Rakefile +5 -0
- data/autotest/discover.rb +1 -0
- data/bin/recurrent +14 -0
- data/lib/recurrent.rb +14 -0
- data/lib/recurrent/configuration.rb +26 -0
- data/lib/recurrent/ice_cube_extensions.rb +22 -0
- data/lib/recurrent/logger.rb +26 -0
- data/lib/recurrent/scheduler.rb +149 -0
- data/lib/recurrent/task.rb +57 -0
- data/lib/recurrent/version.rb +3 -0
- data/lib/recurrent/worker.rb +101 -0
- data/recurrent.gemspec +28 -0
- data/spec/logger_spec.rb +47 -0
- data/spec/scheduler_spec.rb +310 -0
- data/spec/spec_helper.rb +7 -0
- data/spec/task_spec.rb +191 -0
- data/spec/worker_spec.rb +17 -0
- metadata +221 -0
data/.autotest
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
Autotest.add_hook :initialize do |at|
|
2
|
+
at.clear_mappings
|
3
|
+
|
4
|
+
# Run any test that changes
|
5
|
+
at.add_mapping(%r{^spec/.*_spec\.rb$}) do |f, _|
|
6
|
+
[f]
|
7
|
+
end
|
8
|
+
|
9
|
+
# Run tests for any file that changes in lib
|
10
|
+
at.add_mapping(%r{^lib/recurrent((/[^/]+)+)\.rb$}) do |_, m|
|
11
|
+
["spec#{m[1]}_spec.rb"]
|
12
|
+
end
|
13
|
+
end
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2011 Zencoder
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.markdown
ADDED
File without changes
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
Autotest.add_discovery { "rspec2" }
|
data/bin/recurrent
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
$: << File.expand_path("#{File.dirname(__FILE__)}/../lib")
|
2
|
+
require 'rubygems'
|
3
|
+
require 'trollop'
|
4
|
+
begin
|
5
|
+
require 'config/environment'
|
6
|
+
rescue LoadError
|
7
|
+
require 'recurrent'
|
8
|
+
end
|
9
|
+
|
10
|
+
opts = Trollop::options do
|
11
|
+
opt :tasks_file, "File containing task definitions and configuration", :default => 'config/recurrences.rb'
|
12
|
+
end
|
13
|
+
|
14
|
+
Recurrent::Worker.new(opts[:tasks_file]).start
|
data/lib/recurrent.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'ice_cube'
|
2
|
+
begin
|
3
|
+
require 'active_support/time'
|
4
|
+
rescue LoadError
|
5
|
+
require 'active_support'
|
6
|
+
end
|
7
|
+
require 'recurrent/ice_cube_extensions'
|
8
|
+
|
9
|
+
require 'recurrent/configuration'
|
10
|
+
require 'recurrent/logger'
|
11
|
+
require 'recurrent/scheduler'
|
12
|
+
require 'recurrent/task'
|
13
|
+
require 'recurrent/version'
|
14
|
+
require 'recurrent/worker'
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Recurrent
|
2
|
+
class Configuration
|
3
|
+
|
4
|
+
class << self
|
5
|
+
|
6
|
+
attr_accessor :logging, :wait_for_running_tasks_on_exit_for
|
7
|
+
|
8
|
+
def self.block_accessor(*fields)
|
9
|
+
fields.each do |field|
|
10
|
+
attr_writer field
|
11
|
+
eval("
|
12
|
+
def #{field}
|
13
|
+
if block_given?
|
14
|
+
@#{field} = Proc.new
|
15
|
+
else
|
16
|
+
@#{field}
|
17
|
+
end
|
18
|
+
end
|
19
|
+
")
|
20
|
+
end
|
21
|
+
end
|
22
|
+
block_accessor :logger, :save_task_schedule, :load_task_schedule, :save_task_return_value, :process_locking, :handle_slow_task
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module IceCube
|
2
|
+
class Rule
|
3
|
+
def frequency_in_seconds
|
4
|
+
rule_type = self.class
|
5
|
+
if rule_type == IceCube::YearlyRule
|
6
|
+
@interval.years
|
7
|
+
elsif rule_type == IceCube::MonthlyRule
|
8
|
+
@interval.months
|
9
|
+
elsif rule_type == IceCube::WeeklyRule
|
10
|
+
@interval.weeks
|
11
|
+
elsif rule_type == IceCube::DailyRule
|
12
|
+
@interval.days
|
13
|
+
elsif rule_type == IceCube::HourlyRule
|
14
|
+
@interval.hours
|
15
|
+
elsif rule_type == IceCube::MinutelyRule
|
16
|
+
@interval.minutes
|
17
|
+
elsif rule_type == IceCube::SecondlyRule
|
18
|
+
@interval.seconds
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Recurrent
|
2
|
+
class Logger
|
3
|
+
|
4
|
+
attr_reader :identifier
|
5
|
+
|
6
|
+
def initialize(identifier)
|
7
|
+
@identifier = identifier
|
8
|
+
end
|
9
|
+
|
10
|
+
def log_message(message)
|
11
|
+
"[Recurrent - Process:#{@identifier} - Timestamp:#{Time.now.to_s(:seconds)}] - #{message}"
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.define_log_levels(*log_levels)
|
15
|
+
log_levels.each do |log_level|
|
16
|
+
define_method(log_level) do |message|
|
17
|
+
message = log_message(message)
|
18
|
+
puts message unless Configuration.logging == "quiet"
|
19
|
+
Configuration.logger.call(message, log_level) if Configuration.logger
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
define_log_levels :info, :debug, :warn
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,149 @@
|
|
1
|
+
module Recurrent
|
2
|
+
class Scheduler
|
3
|
+
|
4
|
+
attr_accessor :tasks, :logger
|
5
|
+
|
6
|
+
def initialize(task_file=nil)
|
7
|
+
@tasks = []
|
8
|
+
identifier = "host:#{Socket.gethostname} pid:#{Process.pid}" rescue "pid:#{Process.pid}"
|
9
|
+
@logger = Logger.new(identifier)
|
10
|
+
eval(File.read(task_file)) if task_file
|
11
|
+
end
|
12
|
+
|
13
|
+
def configure
|
14
|
+
Configuration
|
15
|
+
end
|
16
|
+
|
17
|
+
def create_rule_from_frequency(frequency)
|
18
|
+
logger.info "| Creating an IceCube Rule"
|
19
|
+
if yearly?(frequency)
|
20
|
+
logger.info "| Creating a yearly rule"
|
21
|
+
IceCube::Rule.yearly(frequency / 1.year)
|
22
|
+
elsif monthly?(frequency)
|
23
|
+
logger.info "| Creating a monthly rule"
|
24
|
+
IceCube::Rule.monthly(frequency / 1.month)
|
25
|
+
elsif weekly?(frequency)
|
26
|
+
logger.info "| Creating a weekly rule"
|
27
|
+
IceCube::Rule.weekly(frequency / 1.week)
|
28
|
+
elsif daily?(frequency)
|
29
|
+
logger.info "| Creating a daily rule"
|
30
|
+
IceCube::Rule.daily(frequency / 1.day)
|
31
|
+
elsif hourly?(frequency)
|
32
|
+
logger.info "| Creating an hourly rule"
|
33
|
+
IceCube::Rule.hourly(frequency / 1.hour)
|
34
|
+
elsif minutely?(frequency)
|
35
|
+
logger.info "| Creating a minutely rule"
|
36
|
+
IceCube::Rule.minutely(frequency / 1.minute)
|
37
|
+
else
|
38
|
+
logger.info "| Creating a secondly rule"
|
39
|
+
IceCube::Rule.secondly(frequency)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def create_schedule(name, frequency, start_time=nil)
|
44
|
+
logger.info "| Creating schedule"
|
45
|
+
if frequency.is_a? IceCube::Rule
|
46
|
+
logger.info "| Frequency is an IceCube Rule: #{frequency.to_s}"
|
47
|
+
rule = frequency
|
48
|
+
frequency_in_seconds = rule.frequency_in_seconds
|
49
|
+
else
|
50
|
+
logger.info "| Frequency is an integer: #{frequency}"
|
51
|
+
rule = create_rule_from_frequency(frequency)
|
52
|
+
logger.info "| IceCube Rule created: #{rule.to_s}"
|
53
|
+
frequency_in_seconds = frequency
|
54
|
+
end
|
55
|
+
start_time ||= derive_start_time(name, frequency_in_seconds)
|
56
|
+
schedule = IceCube::Schedule.new(start_time)
|
57
|
+
schedule.add_recurrence_rule rule
|
58
|
+
logger.info "| schedule created"
|
59
|
+
schedule
|
60
|
+
end
|
61
|
+
|
62
|
+
def derive_start_time(name, frequency)
|
63
|
+
logger.info "| No start time provided, deriving one."
|
64
|
+
if Configuration.load_task_schedule
|
65
|
+
logger.info "| Attempting to derive from saved schedule"
|
66
|
+
derive_start_time_from_saved_schedule(name, frequency)
|
67
|
+
else
|
68
|
+
derive_start_time_from_frequency(frequency)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def derive_start_time_from_saved_schedule(name, frequency)
|
73
|
+
saved_schedule = Configuration.load_task_schedule.call(name)
|
74
|
+
if saved_schedule
|
75
|
+
logger.info "| Saved schedule found"
|
76
|
+
if saved_schedule.rrules.first.frequency_in_seconds == frequency
|
77
|
+
logger.info "| Saved schedule frequency matches, setting start time to saved schedules next occurrence: #{saved_schedule.next_occurrence.to_s(:seconds)}"
|
78
|
+
saved_schedule.next_occurrence
|
79
|
+
else
|
80
|
+
logger.info "| Schedule frequency does not match saved schedule frequency"
|
81
|
+
derive_start_time_from_frequency(frequency)
|
82
|
+
end
|
83
|
+
else
|
84
|
+
derive_start_time_from_frequency(frequency)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def derive_start_time_from_frequency(frequency)
|
89
|
+
logger.info "| Deriving start time from frequency"
|
90
|
+
current_time = Time.now
|
91
|
+
if frequency < 1.minute
|
92
|
+
logger.info "| Setting start time to beginning of current minute"
|
93
|
+
current_time.change(:sec => 0, :usec => 0)
|
94
|
+
elsif frequency < 1.hour
|
95
|
+
logger.info "| Setting start time to beginning of current hour"
|
96
|
+
current_time.change(:min => 0, :sec => 0, :usec => 0)
|
97
|
+
elsif frequency < 1.day
|
98
|
+
logger.info "| Setting start time to beginning of current day"
|
99
|
+
current_time.beginning_of_day
|
100
|
+
elsif frequency < 1.week
|
101
|
+
logger.info "| Setting start time to beginning of current week"
|
102
|
+
current_time.beginning_of_week
|
103
|
+
elsif frequency < 1.month
|
104
|
+
logger.info "| Setting start time to beginning of current month"
|
105
|
+
current_time.beginning_of_month
|
106
|
+
elsif frequency < 1.year
|
107
|
+
logger.info "| Setting start time to beginning of current year"
|
108
|
+
current_time.beginning_of_year
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def every(frequency, key, options={}, &block)
|
113
|
+
logger.info "Adding Task: #{key}"
|
114
|
+
@tasks << Task.new(:name => key,
|
115
|
+
:schedule => create_schedule(key, frequency, options[:start_time]),
|
116
|
+
:action => block,
|
117
|
+
:save => options[:save],
|
118
|
+
:logger => logger)
|
119
|
+
logger.info "| #{key} added to Scheduler"
|
120
|
+
end
|
121
|
+
|
122
|
+
def next_task_time
|
123
|
+
tasks.map { |task| task.next_occurrence }.sort.first
|
124
|
+
end
|
125
|
+
|
126
|
+
def running_tasks
|
127
|
+
tasks.select do |task|
|
128
|
+
task.running?
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
def tasks_at_time(time)
|
133
|
+
tasks.select do |task|
|
134
|
+
task.next_occurrence == time
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def self.define_frequencies(*frequencies)
|
139
|
+
frequencies.each do |frequency|
|
140
|
+
method_name = frequency == :day ? :daily? : :"#{frequency}ly?"
|
141
|
+
define_method(method_name) do |number|
|
142
|
+
(number % 1.send(frequency)) == 0
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
define_frequencies :year, :month, :week, :day, :hour, :minute, :second
|
147
|
+
|
148
|
+
end
|
149
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module Recurrent
|
2
|
+
class Task
|
3
|
+
attr_accessor :action, :name, :logger, :save, :schedule, :thread
|
4
|
+
|
5
|
+
def initialize(options={})
|
6
|
+
@name = options[:name]
|
7
|
+
@schedule = options[:schedule]
|
8
|
+
@action = options[:action]
|
9
|
+
@save = options[:save]
|
10
|
+
@logger = options[:logger]
|
11
|
+
Configuration.save_task_schedule.call(name, schedule) if Configuration.save_task_schedule
|
12
|
+
end
|
13
|
+
|
14
|
+
def execute(execution_time)
|
15
|
+
return handle_still_running(execution_time) if running?
|
16
|
+
@thread = Thread.new do
|
17
|
+
Thread.current["execution_time"] = execution_time
|
18
|
+
return_value = action.call
|
19
|
+
save_results(return_value) if save?
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def handle_still_running(current_time)
|
24
|
+
logger.info "#{name}: Execution from #{thread['execution_time'].to_s(:seconds)} still running, aborting this execution."
|
25
|
+
if Configuration.handle_slow_task
|
26
|
+
Configuration.handle_slow_task.call(name, current_time)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def next_occurrence
|
31
|
+
return @next_occurrence if @next_occurrence && @next_occurrence.future?
|
32
|
+
@next_occurrence = schedule.next_occurrence
|
33
|
+
end
|
34
|
+
|
35
|
+
def save?
|
36
|
+
!!save
|
37
|
+
end
|
38
|
+
|
39
|
+
def save_results(return_value)
|
40
|
+
logger.info "#{name}: Wants to save its return value."
|
41
|
+
if Configuration.save_task_return_value
|
42
|
+
Configuration.save_task_return_value.call(:name => name,
|
43
|
+
:return_value => return_value,
|
44
|
+
:executed_at => thread['execution_time'],
|
45
|
+
:executed_by => logger.identifier)
|
46
|
+
logger.info "#{name}: Return value saved."
|
47
|
+
else
|
48
|
+
logger.info "#{name}: No method to save return values is configured."
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def running?
|
53
|
+
thread.try(:alive?)
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
module Recurrent
|
2
|
+
class Worker
|
3
|
+
|
4
|
+
attr_accessor :scheduler, :logger
|
5
|
+
|
6
|
+
def initialize(task_file=nil)
|
7
|
+
@scheduler = Scheduler.new(task_file)
|
8
|
+
@logger = scheduler.logger
|
9
|
+
end
|
10
|
+
|
11
|
+
def start
|
12
|
+
logger.info "Starting Recurrent"
|
13
|
+
|
14
|
+
trap('TERM') { logger.info 'Waiting for running tasks and exiting...'; $exit = true }
|
15
|
+
trap('INT') { logger.info 'Waiting for running tasks and exiting...'; $exit = true }
|
16
|
+
trap('QUIT') { logger.info 'Waiting for running tasks and exiting...'; $exit = true }
|
17
|
+
|
18
|
+
if Configuration.process_locking
|
19
|
+
execute_with_locking
|
20
|
+
else
|
21
|
+
execute
|
22
|
+
end
|
23
|
+
|
24
|
+
logger.info("Goodbye.")
|
25
|
+
end
|
26
|
+
|
27
|
+
def execute
|
28
|
+
loop do
|
29
|
+
execution_time = scheduler.next_task_time
|
30
|
+
tasks_to_execute = scheduler.tasks_at_time(execution_time)
|
31
|
+
|
32
|
+
wait_for_running_tasks && break if $exit
|
33
|
+
|
34
|
+
wait_until(execution_time)
|
35
|
+
|
36
|
+
wait_for_running_tasks && break if $exit
|
37
|
+
|
38
|
+
tasks_to_execute.each do |task|
|
39
|
+
logger.info "#{task.name}: Executing at #{execution_time.to_s(:seconds)}"
|
40
|
+
task.execute(execution_time)
|
41
|
+
end
|
42
|
+
|
43
|
+
wait_for_running_tasks && break if $exit
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def execute_with_locking
|
48
|
+
lock_established = nil
|
49
|
+
until lock_established
|
50
|
+
break if $exit
|
51
|
+
lock_established = Configuration.process_locking.call('recurrent') do
|
52
|
+
execute
|
53
|
+
end
|
54
|
+
break if $exit
|
55
|
+
logger.info 'Tasks are being monitored by another process. Standing by.'
|
56
|
+
sleep(5)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def wait_for_running_tasks
|
61
|
+
if Configuration.wait_for_running_tasks_on_exit_for
|
62
|
+
wait_for_running_tasks_for(Configuration.wait_for_running_tasks_on_exit_for)
|
63
|
+
else
|
64
|
+
wait_for_running_tasks_indefinitely
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def wait_for_running_tasks_for(seconds)
|
69
|
+
while scheduler.running_tasks.any? do
|
70
|
+
logger.info "Killing running tasks in #{seconds.inspect}."
|
71
|
+
seconds -= 1
|
72
|
+
sleep(1)
|
73
|
+
if seconds == 0
|
74
|
+
scheduler.running_tasks.each do |task|
|
75
|
+
logger.info "Killing #{task.name}."
|
76
|
+
task.thread = nil unless task.thread.try(:kill).try(:alive?)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
true
|
81
|
+
end
|
82
|
+
|
83
|
+
def wait_for_running_tasks_indefinitely
|
84
|
+
if task = scheduler.running_tasks.first
|
85
|
+
logger.info "Waiting for #{task.name} to finish."
|
86
|
+
task.thread.try(:join)
|
87
|
+
wait_for_running_tasks_indefinitely
|
88
|
+
else
|
89
|
+
logger.info "All tasks finished, exiting..."
|
90
|
+
true
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def wait_until(time)
|
95
|
+
until time.past?
|
96
|
+
sleep(0.5) unless $exit
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
end
|
101
|
+
end
|
data/recurrent.gemspec
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require 'recurrent/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "recurrent"
|
7
|
+
s.version = Recurrent::VERSION
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.authors = ["Adam Kittelson"]
|
10
|
+
s.email = ["adam@zencoder.com"]
|
11
|
+
s.homepage = "http://github.com/zencoder/recurrent"
|
12
|
+
s.summary = "Task scheduler that doesn't need to bootstrap your Rails environment every time it executes a task the way running a rake task via cron does."
|
13
|
+
s.description = "Task scheduler that doesn't need to bootstrap your Rails environment every time it executes a task the way running a rake task via cron does."
|
14
|
+
|
15
|
+
s.add_dependency "ice_cube", "0.6.8"
|
16
|
+
s.add_dependency "activesupport"
|
17
|
+
s.add_dependency "i18n"
|
18
|
+
s.add_dependency "trollop"
|
19
|
+
s.add_development_dependency "rspec"
|
20
|
+
s.add_development_dependency "autotest"
|
21
|
+
s.add_development_dependency "timecop"
|
22
|
+
s.add_development_dependency "pry"
|
23
|
+
s.add_development_dependency "pry-doc"
|
24
|
+
s.executables << "recurrent"
|
25
|
+
s.files = `git ls-files`.split("\n")
|
26
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
27
|
+
s.require_path = ["lib"]
|
28
|
+
end
|
data/spec/logger_spec.rb
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Recurrent
|
4
|
+
describe Logger do
|
5
|
+
before(:each) do
|
6
|
+
@logger = Logger.new('logtastic')
|
7
|
+
@users_logger = stub('logger')
|
8
|
+
Configuration.logger do |message, log_level|
|
9
|
+
@users_logger.info(message) if log_level == :info
|
10
|
+
@users_logger.debug(message) if log_level == :debug
|
11
|
+
@users_logger.warn(message) if log_level == :warn
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
after(:all) do
|
16
|
+
Configuration.logger = nil
|
17
|
+
end
|
18
|
+
|
19
|
+
describe "#info" do
|
20
|
+
it "should send a message to the logger with the info logging level" do
|
21
|
+
@users_logger.should_receive(:info).with(@logger.log_message("testing logger"))
|
22
|
+
@logger.info("testing logger")
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
describe "#debug" do
|
27
|
+
it "should send a message to the logger with the debug logging level" do
|
28
|
+
@users_logger.should_receive(:debug).with(@logger.log_message("testing logger"))
|
29
|
+
@logger.debug("testing logger")
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
describe "#warn" do
|
34
|
+
it "should send a message to the logger with the info logging level" do
|
35
|
+
@users_logger.should_receive(:warn).with(@logger.log_message("testing logger"))
|
36
|
+
@logger.warn("testing logger")
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
describe "#log_message" do
|
41
|
+
it "adds the scheduler's identifier to the message" do
|
42
|
+
@logger.log_message("testing").should == "[Recurrent - Process:logtastic - Timestamp:#{Time.now.to_s(:seconds)}] - testing"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,310 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Recurrent
|
4
|
+
describe Scheduler do
|
5
|
+
before(:all) do
|
6
|
+
Configuration.logging = "quiet"
|
7
|
+
end
|
8
|
+
|
9
|
+
describe "schedule creation methods" do
|
10
|
+
before(:all) do
|
11
|
+
@scheduler = Scheduler.new
|
12
|
+
end
|
13
|
+
|
14
|
+
describe "#create_rule_from_frequency" do
|
15
|
+
context "when the frequency is in years" do
|
16
|
+
it "should create a yearly rule" do
|
17
|
+
@scheduler.create_rule_from_frequency(2.years).class.should == IceCube::YearlyRule
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
context "when the frequency is in months" do
|
22
|
+
it "should create a yearly rule" do
|
23
|
+
@scheduler.create_rule_from_frequency(3.months).class.should == IceCube::MonthlyRule
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
context "when the frequency is in weeks" do
|
28
|
+
it "should create a weekly rule" do
|
29
|
+
@scheduler.create_rule_from_frequency(2.weeks).class.should == IceCube::WeeklyRule
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
context "when the frequency is in days" do
|
34
|
+
it "should create a daily rule" do
|
35
|
+
@scheduler.create_rule_from_frequency(3.days).class.should == IceCube::DailyRule
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
context "when the frequency is in hours" do
|
40
|
+
it "should create an hourly rule" do
|
41
|
+
@scheduler.create_rule_from_frequency(6.hours).class.should == IceCube::HourlyRule
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
context "when the frequency is in minutes" do
|
46
|
+
it "should create a minutely rule" do
|
47
|
+
@scheduler.create_rule_from_frequency(10.minutes).class.should == IceCube::MinutelyRule
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
context "when the frequency is in seconds" do
|
52
|
+
it "should create a secondly rule" do
|
53
|
+
@scheduler.create_rule_from_frequency(30.seconds).class.should == IceCube::SecondlyRule
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
describe "create_schedule" do
|
59
|
+
context "when frequency is an IceCube Rule" do
|
60
|
+
subject do
|
61
|
+
rule = IceCube::Rule.daily(1)
|
62
|
+
@scheduler.create_schedule(:test, rule)
|
63
|
+
end
|
64
|
+
it "should be a schedule" do
|
65
|
+
subject.class.should == IceCube::Schedule
|
66
|
+
end
|
67
|
+
it "should have the correct rule" do
|
68
|
+
subject.rrules.first.is_a? IceCube::DailyRule
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
context "when frequency is a number" do
|
73
|
+
subject do
|
74
|
+
@scheduler.create_schedule(:test, 1.day)
|
75
|
+
end
|
76
|
+
it "should be a schedule" do
|
77
|
+
subject.class.should == IceCube::Schedule
|
78
|
+
end
|
79
|
+
it "should have the correct rule" do
|
80
|
+
subject.rrules.first.is_a? IceCube::DailyRule
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
context "when start time is not provided" do
|
85
|
+
it "should derive its own start time" do
|
86
|
+
@scheduler.should_receive(:derive_start_time)
|
87
|
+
@scheduler.create_schedule(:test, 1.day)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
context "when start time is provided" do
|
92
|
+
it "should not derive its own start time" do
|
93
|
+
@scheduler.should_not_receive(:derive_start_time)
|
94
|
+
@scheduler.create_schedule(:test, 1.day, Time.now)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
describe "derive_start_time_from_frequency" do
|
100
|
+
context "when the current time is 11:35:12 am on July 26th, 2011" do
|
101
|
+
before(:all) do
|
102
|
+
Timecop.freeze(Time.local(2011, 7, 26, 11, 35, 12))
|
103
|
+
end
|
104
|
+
|
105
|
+
context "and the frequency is less than a minute" do
|
106
|
+
it "should be 11:35:00, the beginning of the current minute" do
|
107
|
+
start_time = @scheduler.derive_start_time_from_frequency(30.seconds)
|
108
|
+
start_time.should == Time.local(2011, 7, 26, 11, 35, 00)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
context "and the frequency is less than an hour" do
|
113
|
+
it "should be 11:00:00, the beginning of the current hour" do
|
114
|
+
start_time = @scheduler.derive_start_time_from_frequency(15.minutes)
|
115
|
+
start_time.should == Time.local(2011, 7, 26, 11, 00, 00)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
context "and the frequency is less than a day" do
|
120
|
+
it "should be 00:00:00 on July 26th, 2011, the beginning of the current day" do
|
121
|
+
start_time = @scheduler.derive_start_time_from_frequency(3.hours)
|
122
|
+
start_time.should == Time.local(2011, 7, 26, 00, 00, 00)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
context "and the frequency is less than a week" do
|
127
|
+
it "should be 00:00:00 on July 25th, 2011, the beginning of the current week" do
|
128
|
+
start_time = @scheduler.derive_start_time_from_frequency(3.days)
|
129
|
+
start_time.should == Time.local(2011, 7, 25, 00, 00, 00)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
context "and the frequency is less than a month" do
|
134
|
+
it "should be 00:00:00 on July 1st, 2011, the beginning of the current month" do
|
135
|
+
start_time = @scheduler.derive_start_time_from_frequency(10.days)
|
136
|
+
start_time.should == Time.local(2011, 7, 01, 00, 00, 00)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
context "and the frequency is less than a year" do
|
141
|
+
it "should be 00:00:00 on January 1st, 2011, the beginning of the current year" do
|
142
|
+
start_time = @scheduler.derive_start_time_from_frequency(2.months)
|
143
|
+
start_time.should == Time.local(2011, 1, 01, 00, 00, 00)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
after(:all) do
|
148
|
+
Timecop.return
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
describe "derive_start_time_from_saved_schedule" do
|
154
|
+
before(:all) do
|
155
|
+
@scheduler = Scheduler.new
|
156
|
+
Configuration.load_task_schedule do |name|
|
157
|
+
current_time = Time.new
|
158
|
+
current_time.change(:sec => 0, :usec => 0)
|
159
|
+
@scheduler.create_schedule(:test, 10.seconds, current_time) if name == :test
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
describe "a schedule being created with a saved schedule with the same name and frequency" do
|
164
|
+
it "derives its start time from the saved schedule" do
|
165
|
+
@scheduler.should_not_receive(:derive_start_time_from_frequency)
|
166
|
+
@scheduler.create_schedule(:test, 10.seconds)
|
167
|
+
end
|
168
|
+
|
169
|
+
describe "the created schedule's start time" do
|
170
|
+
it "should be the next occurrence of the saved schedule" do
|
171
|
+
saved_schedule = Configuration.load_task_schedule.call(:test)
|
172
|
+
created_schedule = @scheduler.create_schedule(:test, 10.seconds)
|
173
|
+
created_schedule.start_date.to_s(:seconds).should == saved_schedule.next_occurrence.to_s(:seconds)
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
end
|
178
|
+
|
179
|
+
describe "a schedule being created with a saved schedule with the same name and different frequency" do
|
180
|
+
it "derives its start time from the frequency" do
|
181
|
+
@scheduler.should_receive(:derive_start_time_from_frequency)
|
182
|
+
@scheduler.create_schedule(:test, 15.seconds)
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
describe "a schedule being created without a saved schedule" do
|
187
|
+
it "derives its start time from the frequency" do
|
188
|
+
@scheduler.should_receive(:derive_start_time_from_frequency)
|
189
|
+
@scheduler.create_schedule(:new_test, 10.seconds)
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
|
194
|
+
after(:all) do
|
195
|
+
Configuration.load_task_schedule = nil;
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
describe "#next_task_time" do
|
201
|
+
context "when there are multiple tasks" do
|
202
|
+
it "should return the soonest time at which a task is scheduled" do
|
203
|
+
task1 = stub('task1')
|
204
|
+
task1.stub(:next_occurrence).and_return(10.minutes.from_now)
|
205
|
+
task2 = stub('task2')
|
206
|
+
task2.stub(:next_occurrence).and_return(5.minutes.from_now)
|
207
|
+
task3 = stub('task3')
|
208
|
+
task3.stub(:next_occurrence).and_return(15.minutes.from_now)
|
209
|
+
schedule = Scheduler.new
|
210
|
+
schedule.tasks << task1
|
211
|
+
schedule.tasks << task2
|
212
|
+
schedule.tasks << task3
|
213
|
+
schedule.next_task_time.should == task2.next_occurrence
|
214
|
+
end
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
describe "#tasks_at_time" do
|
219
|
+
context "when there are multiple tasks" do
|
220
|
+
it "should return all the tasks whose next_occurrence is at the specified time" do
|
221
|
+
in_five_minutes = 5.minutes.from_now
|
222
|
+
task1 = stub('task1')
|
223
|
+
task1.stub(:next_occurrence).and_return(in_five_minutes)
|
224
|
+
task2 = stub('task2')
|
225
|
+
task2.stub(:next_occurrence).and_return(10.minutes.from_now)
|
226
|
+
task3 = stub('task3')
|
227
|
+
task3.stub(:next_occurrence).and_return(in_five_minutes)
|
228
|
+
schedule = Scheduler.new
|
229
|
+
schedule.tasks << task1
|
230
|
+
schedule.tasks << task2
|
231
|
+
schedule.tasks << task3
|
232
|
+
schedule.tasks_at_time(in_five_minutes).should =~ [task1, task3]
|
233
|
+
end
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
describe "methods created by .define_frequencies" do
|
238
|
+
before :all do
|
239
|
+
@scheduler = Scheduler.new
|
240
|
+
end
|
241
|
+
|
242
|
+
describe "#yearly?" do
|
243
|
+
it "should return true if a frequency is divisible by years" do
|
244
|
+
@scheduler.yearly?(3.years).should == true
|
245
|
+
end
|
246
|
+
|
247
|
+
it "should return false if a frequency is divisible by years" do
|
248
|
+
@scheduler.yearly?(3.days).should == false
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
describe "#monthly?" do
|
253
|
+
it "should return true if a frequency is divisible by months" do
|
254
|
+
@scheduler.monthly?(3.months).should == true
|
255
|
+
end
|
256
|
+
|
257
|
+
it "should return false if a frequency is not divisible by months" do
|
258
|
+
@scheduler.monthly?(3.days).should == false
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
describe "#weekly?" do
|
263
|
+
it "should return true if a frequency is divisible by weeks" do
|
264
|
+
@scheduler.weekly?(3.weeks).should == true
|
265
|
+
end
|
266
|
+
|
267
|
+
it "should return false if a frequency is not divisible by weeks" do
|
268
|
+
@scheduler.weekly?(3.days).should == false
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
describe "#daily?" do
|
273
|
+
it "should return true if a frequency is divisible by days" do
|
274
|
+
@scheduler.daily?(3.days).should == true
|
275
|
+
end
|
276
|
+
|
277
|
+
it "should return false if a frequency is not divisible by days" do
|
278
|
+
@scheduler.daily?(3.hours).should == false
|
279
|
+
end
|
280
|
+
end
|
281
|
+
|
282
|
+
describe "#hourly?" do
|
283
|
+
it "should return true if a frequency is divisible by hours" do
|
284
|
+
@scheduler.hourly?(3.hours).should == true
|
285
|
+
end
|
286
|
+
|
287
|
+
it "should return false if a frequency is not divisible by hours" do
|
288
|
+
@scheduler.hourly?(3.minutes).should == false
|
289
|
+
end
|
290
|
+
end
|
291
|
+
|
292
|
+
describe "#minutely?" do
|
293
|
+
it "should return true if a frequency is divisible by minutes" do
|
294
|
+
@scheduler.minutely?(3.minutes).should == true
|
295
|
+
end
|
296
|
+
|
297
|
+
it "should return false if a frequency is not divisible by years" do
|
298
|
+
@scheduler.minutely?(3.seconds).should == false
|
299
|
+
end
|
300
|
+
end
|
301
|
+
|
302
|
+
describe "#secondly?" do
|
303
|
+
it "should return true if a frequency is divisible by seconds" do
|
304
|
+
@scheduler.secondly?(3.years).should == true
|
305
|
+
end
|
306
|
+
end
|
307
|
+
end
|
308
|
+
|
309
|
+
end
|
310
|
+
end
|
data/spec/spec_helper.rb
ADDED
data/spec/task_spec.rb
ADDED
@@ -0,0 +1,191 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Recurrent
|
4
|
+
describe Task do
|
5
|
+
before(:all) do
|
6
|
+
Configuration.logging = "quiet"
|
7
|
+
end
|
8
|
+
|
9
|
+
describe "#execute" do
|
10
|
+
before :each do
|
11
|
+
@executing_task_time = 5.minutes.ago
|
12
|
+
@current_time = Time.now
|
13
|
+
@task = Task.new :name => 'execute test', :logger => Logger.new('some identifier')
|
14
|
+
end
|
15
|
+
|
16
|
+
context "The task is still running a previous execution" do
|
17
|
+
before :each do
|
18
|
+
@task.thread = Thread.new { Thread.current["execution_time"] = @executing_task_time; sleep(1) }
|
19
|
+
end
|
20
|
+
|
21
|
+
it "calls #handle_still_running and does not execute the task" do
|
22
|
+
@task.should_receive(:handle_still_running).with(@current_time)
|
23
|
+
Thread.should_not_receive(:new)
|
24
|
+
@task.execute(@current_time)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
it "doesn't call #handle_still_running" do
|
29
|
+
@task.should_not_receive(:handle_still_running)
|
30
|
+
@task.execute(@current_time)
|
31
|
+
end
|
32
|
+
|
33
|
+
it "creates a thread" do
|
34
|
+
Thread.should_receive(:new)
|
35
|
+
@task.execute(@current_time)
|
36
|
+
end
|
37
|
+
|
38
|
+
it "sets its execution_time" do
|
39
|
+
@task.execute(@current_time)
|
40
|
+
@task.thread['execution_time'].should == @current_time
|
41
|
+
end
|
42
|
+
|
43
|
+
it "calls the action" do
|
44
|
+
@task.action.should_receive(:call)
|
45
|
+
@task.execute(@current_time)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
describe "#next_occurrence" do
|
50
|
+
context "a task that occurs ever 10 seconds and has just occurred" do
|
51
|
+
subject do
|
52
|
+
current_time = Time.new
|
53
|
+
current_time.change(:sec => 0, :usec => 0)
|
54
|
+
Timecop.freeze(current_time)
|
55
|
+
Task.new(:name => :test, :schedule => Scheduler.new.create_schedule(:test, 10.seconds, current_time))
|
56
|
+
end
|
57
|
+
|
58
|
+
it "should occur 10 seconds from now" do
|
59
|
+
subject.next_occurrence.should == 10.seconds.from_now
|
60
|
+
end
|
61
|
+
|
62
|
+
it "should cache its next occurrence while it's still valid" do
|
63
|
+
subject.schedule.should_receive(:next_occurrence).and_return(10.seconds.from_now)
|
64
|
+
subject.next_occurrence
|
65
|
+
subject.schedule.should_not_receive(:next_occurrence)
|
66
|
+
subject.next_occurrence
|
67
|
+
end
|
68
|
+
|
69
|
+
after(:each) do
|
70
|
+
Timecop.return
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
describe "#handle_still_running" do
|
77
|
+
before(:all) do
|
78
|
+
@executing_task_time = 5.minutes.ago
|
79
|
+
@current_time = Time.now
|
80
|
+
@task = Task.new :name => 'handle_still_running_test', :logger => Logger.new('some identifier')
|
81
|
+
@task.thread = Thread.new { Thread.current["execution_time"] = @executing_task_time }
|
82
|
+
end
|
83
|
+
|
84
|
+
context "When no method for handling a still running task is configured" do
|
85
|
+
it "just logs that the task is still running" do
|
86
|
+
@task.logger.should_receive(:info).with("handle_still_running_test: Execution from #{@executing_task_time.to_s(:seconds)} still running, aborting this execution.")
|
87
|
+
@task.handle_still_running(@current_time)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
context "When a method for handling a still running task is configured" do
|
92
|
+
before(:each) do
|
93
|
+
Configuration.handle_slow_task { |options| 'testing is fun' }
|
94
|
+
end
|
95
|
+
|
96
|
+
it "logs that the task is still running and calls the method" do
|
97
|
+
@task.logger.should_receive(:info).with("handle_still_running_test: Execution from #{@executing_task_time.to_s(:seconds)} still running, aborting this execution.")
|
98
|
+
Configuration.handle_slow_task.should_receive(:call).with('handle_still_running_test', @current_time)
|
99
|
+
@task.handle_still_running(@current_time)
|
100
|
+
end
|
101
|
+
|
102
|
+
after(:each) do
|
103
|
+
Configuration.handle_slow_task = nil
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
describe "#save?" do
|
109
|
+
describe "A task initialized with :save => true" do
|
110
|
+
it "returns true" do
|
111
|
+
Task.new(:save => true).save?.should == true
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
describe "A task initialized with :save => false" do
|
116
|
+
it "returns false" do
|
117
|
+
Task.new(:save => false).save?.should == false
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
describe "A task initialized with no :save option" do
|
122
|
+
it "returns false" do
|
123
|
+
Task.new.save?.should == false
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
describe "#save_results" do
|
129
|
+
context "When no method for saving results is configured" do
|
130
|
+
it "logs that information" do
|
131
|
+
t = Task.new :name => 'save_results_test', :logger => Logger.new('some identifier')
|
132
|
+
t.logger.should_receive(:info).with("save_results_test: Wants to save its return value.")
|
133
|
+
t.logger.should_receive(:info).with("save_results_test: No method to save return values is configured.")
|
134
|
+
t.save_results('some value')
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
context "When a method for saving results is configured" do
|
139
|
+
before(:each) do
|
140
|
+
Configuration.save_task_return_value = lambda { |options| 'testing is fun'}
|
141
|
+
@task = Task.new :name => 'save_results_test', :logger => Logger.new('some identifier')
|
142
|
+
@current_time = Time.now
|
143
|
+
@task.thread = Thread.new { Thread.current["execution_time"] = @current_time }
|
144
|
+
end
|
145
|
+
|
146
|
+
it "calls the method and logs that the value was saved" do
|
147
|
+
@task.logger.should_receive(:info).with("save_results_test: Wants to save its return value.")
|
148
|
+
Configuration.save_task_return_value.should_receive(:call).with(:name => 'save_results_test',
|
149
|
+
:return_value => 'some value',
|
150
|
+
:executed_at => @current_time,
|
151
|
+
:executed_by => 'some identifier')
|
152
|
+
@task.logger.should_receive(:info).with("save_results_test: Return value saved.")
|
153
|
+
@task.save_results('some value')
|
154
|
+
end
|
155
|
+
|
156
|
+
after(:each) do
|
157
|
+
Configuration.save_task_return_value = nil
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
end
|
162
|
+
|
163
|
+
describe "#running?" do
|
164
|
+
describe "A task with a live thread" do
|
165
|
+
it "returns true" do
|
166
|
+
t = Task.new
|
167
|
+
t.thread = Thread.new { sleep 1 }
|
168
|
+
t.running?.should be_true
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
describe "A task with a dead thread" do
|
173
|
+
it "returns false" do
|
174
|
+
t = Task.new
|
175
|
+
t.thread = Thread.new { sleep 1 }
|
176
|
+
t.thread.kill
|
177
|
+
t.running?.should be_false
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
describe "A task with no thread" do
|
182
|
+
it "returns false" do
|
183
|
+
t = Task.new
|
184
|
+
t.thread = nil
|
185
|
+
t.running?.should be_false
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
data/spec/worker_spec.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Recurrent
|
4
|
+
describe Worker do
|
5
|
+
describe "#wait_until" do
|
6
|
+
it "waits until a specified time" do
|
7
|
+
Timecop.freeze(Time.local(2011, 7, 26, 11, 35, 00))
|
8
|
+
waiting_thread = Thread.new { Worker.new.wait_until(Time.local(2011, 7, 26, 11, 40, 00)) }
|
9
|
+
waiting_thread.alive?.should be_true
|
10
|
+
Timecop.travel(Time.local(2011, 7, 26, 11, 40, 00))
|
11
|
+
sleep(0.5)
|
12
|
+
waiting_thread.alive?.should be_false
|
13
|
+
Timecop.return
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
metadata
ADDED
@@ -0,0 +1,221 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: recurrent
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 29
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 1
|
10
|
+
version: 0.0.1
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Adam Kittelson
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2011-08-05 00:00:00 -07:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: ice_cube
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - "="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 23
|
30
|
+
segments:
|
31
|
+
- 0
|
32
|
+
- 6
|
33
|
+
- 8
|
34
|
+
version: 0.6.8
|
35
|
+
type: :runtime
|
36
|
+
version_requirements: *id001
|
37
|
+
- !ruby/object:Gem::Dependency
|
38
|
+
name: activesupport
|
39
|
+
prerelease: false
|
40
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ">="
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
hash: 3
|
46
|
+
segments:
|
47
|
+
- 0
|
48
|
+
version: "0"
|
49
|
+
type: :runtime
|
50
|
+
version_requirements: *id002
|
51
|
+
- !ruby/object:Gem::Dependency
|
52
|
+
name: i18n
|
53
|
+
prerelease: false
|
54
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
55
|
+
none: false
|
56
|
+
requirements:
|
57
|
+
- - ">="
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
hash: 3
|
60
|
+
segments:
|
61
|
+
- 0
|
62
|
+
version: "0"
|
63
|
+
type: :runtime
|
64
|
+
version_requirements: *id003
|
65
|
+
- !ruby/object:Gem::Dependency
|
66
|
+
name: trollop
|
67
|
+
prerelease: false
|
68
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
69
|
+
none: false
|
70
|
+
requirements:
|
71
|
+
- - ">="
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
hash: 3
|
74
|
+
segments:
|
75
|
+
- 0
|
76
|
+
version: "0"
|
77
|
+
type: :runtime
|
78
|
+
version_requirements: *id004
|
79
|
+
- !ruby/object:Gem::Dependency
|
80
|
+
name: rspec
|
81
|
+
prerelease: false
|
82
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
83
|
+
none: false
|
84
|
+
requirements:
|
85
|
+
- - ">="
|
86
|
+
- !ruby/object:Gem::Version
|
87
|
+
hash: 3
|
88
|
+
segments:
|
89
|
+
- 0
|
90
|
+
version: "0"
|
91
|
+
type: :development
|
92
|
+
version_requirements: *id005
|
93
|
+
- !ruby/object:Gem::Dependency
|
94
|
+
name: autotest
|
95
|
+
prerelease: false
|
96
|
+
requirement: &id006 !ruby/object:Gem::Requirement
|
97
|
+
none: false
|
98
|
+
requirements:
|
99
|
+
- - ">="
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
hash: 3
|
102
|
+
segments:
|
103
|
+
- 0
|
104
|
+
version: "0"
|
105
|
+
type: :development
|
106
|
+
version_requirements: *id006
|
107
|
+
- !ruby/object:Gem::Dependency
|
108
|
+
name: timecop
|
109
|
+
prerelease: false
|
110
|
+
requirement: &id007 !ruby/object:Gem::Requirement
|
111
|
+
none: false
|
112
|
+
requirements:
|
113
|
+
- - ">="
|
114
|
+
- !ruby/object:Gem::Version
|
115
|
+
hash: 3
|
116
|
+
segments:
|
117
|
+
- 0
|
118
|
+
version: "0"
|
119
|
+
type: :development
|
120
|
+
version_requirements: *id007
|
121
|
+
- !ruby/object:Gem::Dependency
|
122
|
+
name: pry
|
123
|
+
prerelease: false
|
124
|
+
requirement: &id008 !ruby/object:Gem::Requirement
|
125
|
+
none: false
|
126
|
+
requirements:
|
127
|
+
- - ">="
|
128
|
+
- !ruby/object:Gem::Version
|
129
|
+
hash: 3
|
130
|
+
segments:
|
131
|
+
- 0
|
132
|
+
version: "0"
|
133
|
+
type: :development
|
134
|
+
version_requirements: *id008
|
135
|
+
- !ruby/object:Gem::Dependency
|
136
|
+
name: pry-doc
|
137
|
+
prerelease: false
|
138
|
+
requirement: &id009 !ruby/object:Gem::Requirement
|
139
|
+
none: false
|
140
|
+
requirements:
|
141
|
+
- - ">="
|
142
|
+
- !ruby/object:Gem::Version
|
143
|
+
hash: 3
|
144
|
+
segments:
|
145
|
+
- 0
|
146
|
+
version: "0"
|
147
|
+
type: :development
|
148
|
+
version_requirements: *id009
|
149
|
+
description: Task scheduler that doesn't need to bootstrap your Rails environment every time it executes a task the way running a rake task via cron does.
|
150
|
+
email:
|
151
|
+
- adam@zencoder.com
|
152
|
+
executables:
|
153
|
+
- recurrent
|
154
|
+
extensions: []
|
155
|
+
|
156
|
+
extra_rdoc_files: []
|
157
|
+
|
158
|
+
files:
|
159
|
+
- .autotest
|
160
|
+
- .gitignore
|
161
|
+
- .rspec
|
162
|
+
- Gemfile
|
163
|
+
- LICENSE
|
164
|
+
- README.markdown
|
165
|
+
- Rakefile
|
166
|
+
- autotest/discover.rb
|
167
|
+
- bin/recurrent
|
168
|
+
- lib/recurrent.rb
|
169
|
+
- lib/recurrent/configuration.rb
|
170
|
+
- lib/recurrent/ice_cube_extensions.rb
|
171
|
+
- lib/recurrent/logger.rb
|
172
|
+
- lib/recurrent/scheduler.rb
|
173
|
+
- lib/recurrent/task.rb
|
174
|
+
- lib/recurrent/version.rb
|
175
|
+
- lib/recurrent/worker.rb
|
176
|
+
- recurrent.gemspec
|
177
|
+
- spec/logger_spec.rb
|
178
|
+
- spec/scheduler_spec.rb
|
179
|
+
- spec/spec_helper.rb
|
180
|
+
- spec/task_spec.rb
|
181
|
+
- spec/worker_spec.rb
|
182
|
+
has_rdoc: true
|
183
|
+
homepage: http://github.com/zencoder/recurrent
|
184
|
+
licenses: []
|
185
|
+
|
186
|
+
post_install_message:
|
187
|
+
rdoc_options: []
|
188
|
+
|
189
|
+
require_paths:
|
190
|
+
- - lib
|
191
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
192
|
+
none: false
|
193
|
+
requirements:
|
194
|
+
- - ">="
|
195
|
+
- !ruby/object:Gem::Version
|
196
|
+
hash: 3
|
197
|
+
segments:
|
198
|
+
- 0
|
199
|
+
version: "0"
|
200
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
201
|
+
none: false
|
202
|
+
requirements:
|
203
|
+
- - ">="
|
204
|
+
- !ruby/object:Gem::Version
|
205
|
+
hash: 3
|
206
|
+
segments:
|
207
|
+
- 0
|
208
|
+
version: "0"
|
209
|
+
requirements: []
|
210
|
+
|
211
|
+
rubyforge_project:
|
212
|
+
rubygems_version: 1.3.9.2
|
213
|
+
signing_key:
|
214
|
+
specification_version: 3
|
215
|
+
summary: Task scheduler that doesn't need to bootstrap your Rails environment every time it executes a task the way running a rake task via cron does.
|
216
|
+
test_files:
|
217
|
+
- spec/logger_spec.rb
|
218
|
+
- spec/scheduler_spec.rb
|
219
|
+
- spec/spec_helper.rb
|
220
|
+
- spec/task_spec.rb
|
221
|
+
- spec/worker_spec.rb
|