moment 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README +5 -0
- data/bin/moment_server +81 -0
- data/lib/client.rb +24 -0
- data/lib/cron_trigger.rb +161 -0
- data/lib/job.rb +9 -0
- data/lib/moment.rb +70 -0
- data/lib/scheduler.rb +78 -0
- data/lib/server.rb +39 -0
- data/lib/shell_job.rb +17 -0
- data/lib/simple_trigger.rb +30 -0
- data/lib/trigger.rb +9 -0
- data/test/cron_trigger_test.rb +105 -0
- data/test/job_test.rb +13 -0
- data/test/moment_test.rb +9 -0
- data/test/scheduler_test.rb +64 -0
- data/test/server_test.rb +53 -0
- data/test/shell_job_test.rb +19 -0
- data/test/simple_trigger_test.rb +16 -0
- data/test/trigger_test.rb +11 -0
- metadata +64 -0
data/README
ADDED
data/bin/moment_server
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
|
3
|
+
#
|
4
|
+
# == Synopsis
|
5
|
+
#
|
6
|
+
# moment_server: scheduling server, accessible via DRb
|
7
|
+
#
|
8
|
+
# $ moment_server -p 9901 saved_jobs
|
9
|
+
# Loading previous jobs from saved_jobs..
|
10
|
+
# Loaded 0 jobs:
|
11
|
+
# []
|
12
|
+
# Starting service on druby://localhost:9901 ...
|
13
|
+
# Ready.<Ctrl-C>
|
14
|
+
# Saving 0 jobs to saved_jobs..
|
15
|
+
# Exiting..
|
16
|
+
#
|
17
|
+
# == Usage
|
18
|
+
#
|
19
|
+
# moment_server [@options] [job_file]
|
20
|
+
#
|
21
|
+
# -h, --help:
|
22
|
+
# show help
|
23
|
+
#
|
24
|
+
# -p, --port: (default 9000)
|
25
|
+
# port to run druby instance on
|
26
|
+
#
|
27
|
+
# -q, --[no-]quiet: (default false)
|
28
|
+
# run verbosely
|
29
|
+
#
|
30
|
+
# job_file
|
31
|
+
# if specified, will save still running jobs in the given file on
|
32
|
+
# exit to be read in again on next start.
|
33
|
+
#
|
34
|
+
|
35
|
+
require 'rubygems'
|
36
|
+
require 'moment'
|
37
|
+
require 'drb'
|
38
|
+
require 'ostruct'
|
39
|
+
require 'optparse'
|
40
|
+
require 'rdoc/usage'
|
41
|
+
|
42
|
+
@options = OpenStruct.new :port => 9000, :quiet => false, :file => nil
|
43
|
+
OptionParser.new do |o|
|
44
|
+
o.on '-p', '--port [port]', Integer do |port| @options.port = port end
|
45
|
+
o.on '-q', '--[no-]quiet' do |quiet| @options.quiet = quiet end
|
46
|
+
o.on '-h', '--help' do
|
47
|
+
RDoc::usage
|
48
|
+
exit
|
49
|
+
end
|
50
|
+
end.order! do |file|
|
51
|
+
@options.file ||= file
|
52
|
+
end
|
53
|
+
|
54
|
+
def putsv(*args)
|
55
|
+
puts args unless @options.quiet
|
56
|
+
end
|
57
|
+
|
58
|
+
url = "druby://localhost:#{@options.port}"
|
59
|
+
server = Moment::Server.new
|
60
|
+
|
61
|
+
unless @options.file.nil? or !File.exists?(@options.file)
|
62
|
+
putsv "Loading previous jobs from #{@options.file}.."
|
63
|
+
server.load(File.open(@options.file))
|
64
|
+
putsv "Loaded #{server.jobs.size} jobs:", server.jobs.inspect
|
65
|
+
end
|
66
|
+
|
67
|
+
server.run
|
68
|
+
|
69
|
+
putsv "Starting service on #{url} ..."
|
70
|
+
DRb.start_service(url, server)
|
71
|
+
begin
|
72
|
+
putsv "Ready."
|
73
|
+
DRb.thread.join
|
74
|
+
rescue Interrupt => e
|
75
|
+
if @options.file
|
76
|
+
putsv "Saving #{server.jobs.size} jobs to #{@options.file}.."
|
77
|
+
server.dump(File.open(@options.file, 'w'))
|
78
|
+
end
|
79
|
+
|
80
|
+
putsv "Exiting.."
|
81
|
+
end
|
data/lib/client.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'drb'
|
2
|
+
require 'shell_job'
|
3
|
+
require 'simple_trigger'
|
4
|
+
|
5
|
+
class Printer
|
6
|
+
|
7
|
+
include DRbUndumped
|
8
|
+
attr_accessor :name, :counter
|
9
|
+
|
10
|
+
def initialize(name = nil)
|
11
|
+
self.name = name
|
12
|
+
self.counter = 0
|
13
|
+
end
|
14
|
+
|
15
|
+
def execute
|
16
|
+
puts self.inspect, Time.now
|
17
|
+
@counter += 1
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
DRb.start_service
|
23
|
+
scheduler = DRbObject.new(nil, 'druby://localhost:9000')
|
24
|
+
scheduler.schedule(Moment::ShellJob.new('ls'), Moment::SimpleTrigger.new(Time.now, Time.now + 10, 2))
|
data/lib/cron_trigger.rb
ADDED
@@ -0,0 +1,161 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/moment'
|
2
|
+
require File.dirname(__FILE__) + '/trigger'
|
3
|
+
|
4
|
+
class Moment::CronTrigger < Moment::Trigger
|
5
|
+
|
6
|
+
attr_reader :sec, :min, :hour, :day, :month, :wday, :year, :cron_expr
|
7
|
+
|
8
|
+
def initialize(expr)
|
9
|
+
super()
|
10
|
+
self.cron_expr = expr
|
11
|
+
end
|
12
|
+
|
13
|
+
def cron_expr=(expr)
|
14
|
+
@cron_expr = expr
|
15
|
+
self.sec, self.min, self.hour, self.day, self.month, self.wday, self.year = @cron_expr.split(' ')
|
16
|
+
#puts inspect
|
17
|
+
end
|
18
|
+
|
19
|
+
def fire_time_after(time)
|
20
|
+
sec, min, hour, day, month, year, wday, yday, isdst, zone = time.to_a
|
21
|
+
|
22
|
+
loop do
|
23
|
+
# year
|
24
|
+
unless @year.nil? or @year.include?(year)
|
25
|
+
return nil if year > @year.max
|
26
|
+
year = @year.detect do |y| y > year end # next allowable year
|
27
|
+
end
|
28
|
+
|
29
|
+
# month
|
30
|
+
unless @month.include?(month)
|
31
|
+
# next allowable month
|
32
|
+
next_month = @month.detect(lambda { @month.min }) do |m| m > month end
|
33
|
+
# reset everything lower
|
34
|
+
day, hour, min, sec = @day.min, @hour.min, @min.min, @sec.min
|
35
|
+
# carry case
|
36
|
+
if next_month < month
|
37
|
+
month = next_month
|
38
|
+
year += 1
|
39
|
+
retry
|
40
|
+
end
|
41
|
+
month = next_month
|
42
|
+
end
|
43
|
+
|
44
|
+
# day
|
45
|
+
month_days = (1 .. month_days(year, month))
|
46
|
+
days = @day.select do |d| month_days === d end
|
47
|
+
unless days.include?(day)
|
48
|
+
next_day = days.detect(lambda { days.min }) do |d| d > day end
|
49
|
+
hour, min, sec = @hour.min, @min.min, @sec.min
|
50
|
+
if next_day.nil? or next_day < day
|
51
|
+
day = next_day.nil? ? @day.min : next_day
|
52
|
+
month += 1
|
53
|
+
retry
|
54
|
+
end
|
55
|
+
day = next_day
|
56
|
+
end
|
57
|
+
|
58
|
+
# hour
|
59
|
+
unless @hour.include?(hour)
|
60
|
+
next_hour = @hour.detect(lambda { @hour.min }) do |h| h > hour end
|
61
|
+
min, sec = @min.min, @sec.min
|
62
|
+
if next_hour < hour
|
63
|
+
hour = next_hour
|
64
|
+
day += 1
|
65
|
+
retry
|
66
|
+
end
|
67
|
+
hour = next_hour
|
68
|
+
end
|
69
|
+
|
70
|
+
# min
|
71
|
+
unless @min.include?(min)
|
72
|
+
next_min = @min.detect(lambda { @min.min }) do |m| m > min end
|
73
|
+
sec = @sec.min
|
74
|
+
if next_min < min
|
75
|
+
min = next_min
|
76
|
+
hour += 1
|
77
|
+
retry
|
78
|
+
end
|
79
|
+
min = next_min
|
80
|
+
end
|
81
|
+
|
82
|
+
# sec
|
83
|
+
unless @sec.include?(sec)
|
84
|
+
next_sec = @sec.detect(lambda { @sec.min }) do |s| s > sec end
|
85
|
+
if next_sec < sec
|
86
|
+
sec = next_sec
|
87
|
+
min += 1
|
88
|
+
retry
|
89
|
+
end
|
90
|
+
sec = next_sec
|
91
|
+
end
|
92
|
+
|
93
|
+
break
|
94
|
+
end
|
95
|
+
|
96
|
+
Time.local sec, min, hour, day, month, year, wday, yday, isdst, zone
|
97
|
+
end
|
98
|
+
|
99
|
+
# TODO: mimic attr_reader to define all of these
|
100
|
+
def sec=(sec)
|
101
|
+
@sec = parse_part(sec, 0 .. 59)
|
102
|
+
end
|
103
|
+
|
104
|
+
def min=(min)
|
105
|
+
@min = parse_part(min, 0 .. 59)
|
106
|
+
end
|
107
|
+
|
108
|
+
def hour=(hour)
|
109
|
+
@hour = parse_part(hour, 0 .. 23)
|
110
|
+
end
|
111
|
+
|
112
|
+
def day=(day)
|
113
|
+
@day = parse_part(day, 1 .. 31)
|
114
|
+
end
|
115
|
+
|
116
|
+
def month=(month)
|
117
|
+
@month = parse_part(month, 1 .. 12)
|
118
|
+
end
|
119
|
+
|
120
|
+
def year=(year)
|
121
|
+
@year = parse_part(year)
|
122
|
+
end
|
123
|
+
|
124
|
+
def wday=(wday)
|
125
|
+
@wday = parse_part(wday, 0 .. 6)
|
126
|
+
end
|
127
|
+
|
128
|
+
LeapYearMonthDays = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
|
129
|
+
CommonYearMonthDays = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
|
130
|
+
private
|
131
|
+
def month_days(y, m)
|
132
|
+
if ((y % 4 == 0) && (y % 100 != 0)) || (y % 400 == 0)
|
133
|
+
LeapYearMonthDays[m-1]
|
134
|
+
else
|
135
|
+
CommonYearMonthDays[m-1]
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
# 0-5,8,10; 0-5; *; */5
|
140
|
+
def parse_part(part, range=nil)
|
141
|
+
return range if part.nil? or part == '*' or part =~ /[*0]\/1/
|
142
|
+
|
143
|
+
r = Array.new
|
144
|
+
part.split(',').each do |p|
|
145
|
+
if p =~ /-/ # 0-5
|
146
|
+
r << Range.new(*p.scan(/\d+/)).to_a.map do |x| x.to_i end
|
147
|
+
elsif p =~ /(\*|\d+)\/(\d+)/ and not range.nil? # */5, 2/10
|
148
|
+
min = $1 == '*' ? 0 : $1.to_i
|
149
|
+
inc = $2.to_i
|
150
|
+
(min .. range.end).each_with_index do |x, i|
|
151
|
+
r << x if i % inc == 0
|
152
|
+
end
|
153
|
+
else
|
154
|
+
r << p.to_i
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
r.flatten
|
159
|
+
end
|
160
|
+
|
161
|
+
end
|
data/lib/job.rb
ADDED
data/lib/moment.rb
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
#
|
2
|
+
# == Moment. a scheduler.
|
3
|
+
#
|
4
|
+
# Author:: visnup <visnux@swivel.com>
|
5
|
+
# Started:: 8 March 2006
|
6
|
+
# Copyright:: Copyright (c) 2006 Swivel, LLC
|
7
|
+
# License:: Ruby License
|
8
|
+
#
|
9
|
+
# i just ate some doritos and need to drink more water.
|
10
|
+
#
|
11
|
+
# === Introduction
|
12
|
+
#
|
13
|
+
# Moment is a scheduling service that handles running given jobs at specified
|
14
|
+
# intervals.
|
15
|
+
#
|
16
|
+
# === Features
|
17
|
+
#
|
18
|
+
# Built-in triggers include a cron implementation (CronTrigger) and simple
|
19
|
+
# reptition (SimpleTrigger). Users-written triggers are of course supported by
|
20
|
+
# extending Trigger. A Server is also provided that handles multithreading.
|
21
|
+
# There is also a seperate executable +moment_server+ that can be run and
|
22
|
+
# scheduled to via DRb. Built-in job types include a shell execution job
|
23
|
+
# (ShellJob), or you can extend Job.
|
24
|
+
#
|
25
|
+
# === Example
|
26
|
+
#
|
27
|
+
# require 'rubygems'
|
28
|
+
# require 'moment'
|
29
|
+
# require 'drb'
|
30
|
+
#
|
31
|
+
# class Printer
|
32
|
+
#
|
33
|
+
# include DRbUndumped
|
34
|
+
# attr_accessor :name, :counter
|
35
|
+
#
|
36
|
+
# def initialize(name = nil)
|
37
|
+
# self.name = name
|
38
|
+
# self.counter = 0
|
39
|
+
# end
|
40
|
+
#
|
41
|
+
# def execute
|
42
|
+
# puts self.inspect, Time.now
|
43
|
+
# @counter += 1
|
44
|
+
# end
|
45
|
+
#
|
46
|
+
# end
|
47
|
+
#
|
48
|
+
# DRb.start_service
|
49
|
+
# scheduler = DRbObject.new(nil, 'druby://localhost:9901')
|
50
|
+
# scheduler.schedule(Moment::ShellJob.new('ls'),
|
51
|
+
# Moment::SimpleTrigger.new(Time.now, Time.now + 10, 2)) # from now till 10 seconds from now, every 2 seconds
|
52
|
+
# scheduler.schedule(Moment::ShellJob.new('date'),
|
53
|
+
# Moment::CronTrigger.new('0 0/5 * * 3,5') # every 5 minutes during March and May
|
54
|
+
#
|
55
|
+
# === Server
|
56
|
+
#
|
57
|
+
# The above example assumes the included server +moment_server+ is running on
|
58
|
+
# port 9901. See the documentation on running +moment_server+.
|
59
|
+
#
|
60
|
+
|
61
|
+
module Moment
|
62
|
+
end
|
63
|
+
|
64
|
+
require File.dirname(__FILE__) + '/cron_trigger.rb'
|
65
|
+
require File.dirname(__FILE__) + '/job.rb'
|
66
|
+
require File.dirname(__FILE__) + '/scheduler.rb'
|
67
|
+
require File.dirname(__FILE__) + '/server.rb'
|
68
|
+
require File.dirname(__FILE__) + '/shell_job.rb'
|
69
|
+
require File.dirname(__FILE__) + '/simple_trigger.rb'
|
70
|
+
require File.dirname(__FILE__) + '/trigger.rb'
|
data/lib/scheduler.rb
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/moment'
|
2
|
+
|
3
|
+
class Moment::Scheduler
|
4
|
+
|
5
|
+
Event = Struct.new(:job, :trigger)
|
6
|
+
|
7
|
+
attr_accessor :exit_on_idle
|
8
|
+
attr_reader :jobs
|
9
|
+
|
10
|
+
def initialize(exit_on_idle = true)
|
11
|
+
self.exit_on_idle = exit_on_idle
|
12
|
+
@jobs = Array.new
|
13
|
+
@now = Time.now
|
14
|
+
end
|
15
|
+
|
16
|
+
def schedule(job, trigger = nil)
|
17
|
+
job_id = @jobs.size
|
18
|
+
@jobs[job_id] = Event.new(job, trigger)
|
19
|
+
|
20
|
+
job_id
|
21
|
+
end
|
22
|
+
|
23
|
+
def unschedule(job_id)
|
24
|
+
@jobs.delete_at(job_id)
|
25
|
+
end
|
26
|
+
|
27
|
+
def clean_up
|
28
|
+
@jobs.delete_if do |entry| entry.trigger.fire_time_after(@now).nil? end
|
29
|
+
end
|
30
|
+
|
31
|
+
def run
|
32
|
+
loop do
|
33
|
+
# find the next firing triggers
|
34
|
+
todo = next_jobs
|
35
|
+
if todo.empty? # nothing to do, so we wait
|
36
|
+
break if @exit_on_idle
|
37
|
+
sleep
|
38
|
+
retry # check again if we're woken up
|
39
|
+
end
|
40
|
+
|
41
|
+
# sleep until the next event; if we get woken up, retry from the start
|
42
|
+
pause_time = todo.first.trigger.fire_time_after(@now) - Time.now
|
43
|
+
retry if (pause_time > 0 and sleep(pause_time) < pause_time)
|
44
|
+
|
45
|
+
todo.each do |entry|
|
46
|
+
next unless @jobs.index(entry) # make sure since we were sleeping
|
47
|
+
entry.job.execute rescue puts $!
|
48
|
+
end
|
49
|
+
|
50
|
+
# make sure next call to next_jobs won't return the exact same result
|
51
|
+
sleep 0.5 # TODO this is dangerous; could skip over some jobs
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# find the next wakeup triggers in our job/trigger list
|
56
|
+
def next_jobs
|
57
|
+
@now = Time.now
|
58
|
+
earliest = nil
|
59
|
+
jobs = Array.new
|
60
|
+
|
61
|
+
@jobs.each do |entry|
|
62
|
+
job, trigger = entry.job, entry.trigger
|
63
|
+
time = trigger.fire_time_after(@now)
|
64
|
+
case
|
65
|
+
when time.nil?
|
66
|
+
# do nothing
|
67
|
+
when earliest.nil?, time < earliest
|
68
|
+
earliest = time
|
69
|
+
jobs = [ entry ]
|
70
|
+
when time == earliest
|
71
|
+
jobs << entry
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
jobs
|
76
|
+
end
|
77
|
+
|
78
|
+
end # class Moment::Scheduler
|
data/lib/server.rb
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/moment'
|
2
|
+
require File.dirname(__FILE__) + '/scheduler'
|
3
|
+
require File.dirname(__FILE__) + '/trigger'
|
4
|
+
require File.dirname(__FILE__) + '/job'
|
5
|
+
require File.dirname(__FILE__) + '/shell_job'
|
6
|
+
require File.dirname(__FILE__) + '/simple_trigger'
|
7
|
+
require File.dirname(__FILE__) + '/cron_trigger'
|
8
|
+
|
9
|
+
class Moment::Server < Moment::Scheduler
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
super(false) # don't exit on idle
|
13
|
+
@threads = Array.new
|
14
|
+
end
|
15
|
+
|
16
|
+
def run
|
17
|
+
@threads << Thread.new do
|
18
|
+
super
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def schedule(job, trigger)
|
23
|
+
puts 'scheduling:', job.inspect, trigger.inspect
|
24
|
+
retval = super
|
25
|
+
@threads.each do |t| t.wakeup end
|
26
|
+
retval
|
27
|
+
end
|
28
|
+
|
29
|
+
def dump(file)
|
30
|
+
file << Marshal.dump(@jobs)
|
31
|
+
end
|
32
|
+
|
33
|
+
def load(file)
|
34
|
+
@jobs = Marshal.load(file.read)
|
35
|
+
rescue ArgumentError
|
36
|
+
# ignore if the file is empty
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
data/lib/shell_job.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/job'
|
2
|
+
|
3
|
+
class Moment::ShellJob < Moment::Job
|
4
|
+
|
5
|
+
attr_accessor :command
|
6
|
+
attr_reader :last_result
|
7
|
+
|
8
|
+
def initialize(command)
|
9
|
+
self.command = command
|
10
|
+
@last_result = nil
|
11
|
+
end
|
12
|
+
|
13
|
+
def execute
|
14
|
+
@last_result = `#{command}`
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/moment'
|
2
|
+
require File.dirname(__FILE__) + '/trigger'
|
3
|
+
|
4
|
+
class Moment::SimpleTrigger < Moment::Trigger
|
5
|
+
|
6
|
+
attr_accessor :start_time, :end_time, :repeat_interval
|
7
|
+
|
8
|
+
def initialize(start_time = nil, end_time = nil, repeat_interval = nil)
|
9
|
+
super()
|
10
|
+
self.start_time = start_time
|
11
|
+
self.end_time = end_time
|
12
|
+
self.repeat_interval = repeat_interval
|
13
|
+
end
|
14
|
+
|
15
|
+
def fire_time_after(time)
|
16
|
+
@start_time = time if not @start_time
|
17
|
+
|
18
|
+
case
|
19
|
+
when end_time && time > end_time
|
20
|
+
nil
|
21
|
+
when time < start_time
|
22
|
+
start_time
|
23
|
+
when repeat_interval != nil && repeat_interval > 0
|
24
|
+
time + repeat_interval - ((time - start_time) % repeat_interval)
|
25
|
+
else
|
26
|
+
nil
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
data/lib/trigger.rb
ADDED
@@ -0,0 +1,105 @@
|
|
1
|
+
|
2
|
+
require 'test/unit'
|
3
|
+
require File.dirname(__FILE__) + '/../lib/cron_trigger'
|
4
|
+
|
5
|
+
class CronTriggerTest < Test::Unit::TestCase
|
6
|
+
|
7
|
+
def test_create
|
8
|
+
expr = '0 0 0 * *'
|
9
|
+
cron = Moment::CronTrigger.new(expr)
|
10
|
+
assert_equal(expr, cron.cron_expr)
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_explicit_no_carry
|
14
|
+
input = Time.local(0, 0, 0, 1, 1, 2004, nil, nil, false, 'UTC')
|
15
|
+
cron = Moment::CronTrigger.new('10 5 4 3 2')
|
16
|
+
expected = Time.local(10, 5, 4, 3, 2, 2004, nil, nil, false, 'UTC')
|
17
|
+
assert_equal(expected, cron.fire_time_after(input))
|
18
|
+
end
|
19
|
+
|
20
|
+
def test_explicit_no_carry_day
|
21
|
+
input = Time.local(0, 0, 0, 1, 1, 2004, nil, nil, false, 'UTC')
|
22
|
+
cron = Moment::CronTrigger.new('10 5 4 3 1')
|
23
|
+
expected = Time.local(10, 5, 4, 3, 1, 2004, nil, nil, false, 'UTC')
|
24
|
+
assert_equal(expected, cron.fire_time_after(input))
|
25
|
+
end
|
26
|
+
|
27
|
+
def test_explicit_no_carry_hour
|
28
|
+
input = Time.local(0, 0, 0, 1, 1, 2004, nil, nil, false, 'UTC')
|
29
|
+
cron = Moment::CronTrigger.new('10 5 4 1 1')
|
30
|
+
expected = Time.local(10, 5, 4, 1, 1, 2004, nil, nil, false, 'UTC')
|
31
|
+
assert_equal(expected, cron.fire_time_after(input))
|
32
|
+
end
|
33
|
+
|
34
|
+
def test_explicit_no_carry_min
|
35
|
+
input = Time.local(0, 0, 0, 1, 1, 2004, nil, nil, false, 'UTC')
|
36
|
+
cron = Moment::CronTrigger.new('10 5 0 1 1')
|
37
|
+
expected = Time.local(10, 5, 0, 1, 1, 2004, nil, nil, false, 'UTC')
|
38
|
+
assert_equal(expected, cron.fire_time_after(input))
|
39
|
+
end
|
40
|
+
|
41
|
+
def test_explicit_with_carry
|
42
|
+
input = Time.local(0, 1, 23, 1, 1, 2004, nil, nil, false, 'UTC')
|
43
|
+
cron = Moment::CronTrigger.new('0 0 23 1 1')
|
44
|
+
expected = Time.local(0, 0, 23, 1, 1, 2005, nil, nil, false, 'UTC')
|
45
|
+
assert_equal(expected, cron.fire_time_after(input))
|
46
|
+
end
|
47
|
+
|
48
|
+
def test_implicit_with_carry
|
49
|
+
input = Time.local(0, 1, 23, 1, 1, 2004, nil, nil, false, 'UTC')
|
50
|
+
cron = Moment::CronTrigger.new('0 0 * * *')
|
51
|
+
expected = Time.local(0, 0, 0, 2, 1, 2004, nil, nil, false, 'UTC')
|
52
|
+
assert_equal(expected, cron.fire_time_after(input))
|
53
|
+
end
|
54
|
+
|
55
|
+
def test_year_before
|
56
|
+
input = Time.local(0, 1, 23, 1, 1, 2004, nil, nil, false, 'UTC')
|
57
|
+
cron = Moment::CronTrigger.new('0 15 10 * * * 2003')
|
58
|
+
expected = nil
|
59
|
+
assert_equal(expected, cron.fire_time_after(input))
|
60
|
+
end
|
61
|
+
|
62
|
+
def test_year_after
|
63
|
+
input = Time.local(0, 1, 23, 1, 1, 2004, nil, nil, false, 'UTC')
|
64
|
+
cron = Moment::CronTrigger.new('0 15 10 * * * 2005')
|
65
|
+
expected = Time.local(0, 15, 10, 2, 1, 2005, nil, nil, false, 'UTC')
|
66
|
+
assert_equal(expected, cron.fire_time_after(input))
|
67
|
+
end
|
68
|
+
|
69
|
+
def test_month_over
|
70
|
+
input = Time.local(0, 1, 23, 2, 12, 2004, nil, nil, false, 'UTC')
|
71
|
+
cron = Moment::CronTrigger.new('0 15 10 1 * * *')
|
72
|
+
expected = Time.local(0, 15, 10, 1, 1, 2005, nil, nil, false, 'UTC')
|
73
|
+
assert_equal(expected, cron.fire_time_after(input))
|
74
|
+
end
|
75
|
+
|
76
|
+
def test_day_over
|
77
|
+
input = Time.local(0, 1, 23, 30, 11, 2004, nil, nil, false, 'UTC')
|
78
|
+
cron = Moment::CronTrigger.new('* * * 31 * * *')
|
79
|
+
expected = Time.local(0, 0, 0, 31, 12, 2004, nil, nil, false, 'UTC')
|
80
|
+
assert_equal(expected, cron.fire_time_after(input))
|
81
|
+
end
|
82
|
+
|
83
|
+
def test_min_over
|
84
|
+
input = Time.local(2, 59, 12, 30, 11, 2004, nil, nil, false, 'UTC')
|
85
|
+
cron = Moment::CronTrigger.new('1 * * * * * *')
|
86
|
+
expected = Time.local(1, 0, 13, 30, 11, 2004, nil, nil, false, 'UTC')
|
87
|
+
assert_equal(expected, cron.fire_time_after(input))
|
88
|
+
end
|
89
|
+
|
90
|
+
def test_range
|
91
|
+
input0 = Time.local(2, 59, 12, 30, 11, 2004, nil, nil, false, 'UTC')
|
92
|
+
cron = Moment::CronTrigger.new('28/5,59 1-4,6,20 */1 * 5,0/1 * *')
|
93
|
+
assert_equal([28,33,38,43,48,53,58,59], cron.sec)
|
94
|
+
assert_equal([1,2,3,4,6,20], cron.min)
|
95
|
+
assert_equal((0 .. 23), cron.hour)
|
96
|
+
assert_equal((1 .. 12), cron.month)
|
97
|
+
|
98
|
+
expected0 = Time.local(28, 1, 13, 30, 11, 2004, nil, nil, false, 'UTC')
|
99
|
+
input1 = Time.local(29, 1, 13, 30, 11, 2004, nil, nil, false, 'UTC')
|
100
|
+
expected1 = Time.local(33, 1, 13, 30, 11, 2004, nil, nil, false, 'UTC')
|
101
|
+
assert_equal(expected0, cron.fire_time_after(input0))
|
102
|
+
assert_equal(expected1, cron.fire_time_after(input1))
|
103
|
+
end
|
104
|
+
|
105
|
+
end
|
data/test/job_test.rb
ADDED
data/test/moment_test.rb
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
|
3
|
+
require File.dirname(__FILE__) + '/cron_trigger_test'
|
4
|
+
require File.dirname(__FILE__) + '/job_test'
|
5
|
+
require File.dirname(__FILE__) + '/scheduler_test'
|
6
|
+
require File.dirname(__FILE__) + '/server_test'
|
7
|
+
require File.dirname(__FILE__) + '/shell_job_test'
|
8
|
+
require File.dirname(__FILE__) + '/simple_trigger_test'
|
9
|
+
require File.dirname(__FILE__) + '/trigger_test'
|
@@ -0,0 +1,64 @@
|
|
1
|
+
|
2
|
+
require 'test/unit'
|
3
|
+
require File.dirname(__FILE__) + '/../lib/scheduler'
|
4
|
+
require File.dirname(__FILE__) + '/../lib/simple_trigger'
|
5
|
+
require File.dirname(__FILE__) + '/../lib/job'
|
6
|
+
|
7
|
+
class SchedulerTest < Test::Unit::TestCase
|
8
|
+
|
9
|
+
def test_initialize
|
10
|
+
scheduler = Moment::Scheduler.new
|
11
|
+
assert_equal([], scheduler.jobs)
|
12
|
+
end
|
13
|
+
|
14
|
+
class CountExecuted < Moment::Job
|
15
|
+
attr_reader :executed
|
16
|
+
def initialize
|
17
|
+
@executed = 0
|
18
|
+
end
|
19
|
+
def execute
|
20
|
+
@executed += 1
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_schedule
|
25
|
+
scheduler = Moment::Scheduler.new
|
26
|
+
simple = Moment::SimpleTrigger.new(Time.now+1, Time.now+2, nil)
|
27
|
+
job = CountExecuted.new
|
28
|
+
|
29
|
+
scheduler.schedule(job, simple)
|
30
|
+
scheduler.schedule(job, simple)
|
31
|
+
scheduler.run
|
32
|
+
|
33
|
+
assert_not_equal(0, job.executed)
|
34
|
+
end
|
35
|
+
|
36
|
+
def test_unschedule
|
37
|
+
scheduler = Moment::Scheduler.new
|
38
|
+
simple = Moment::SimpleTrigger.new(nil, nil, 1)
|
39
|
+
job = CountExecuted.new
|
40
|
+
|
41
|
+
job_id = scheduler.schedule(job, simple)
|
42
|
+
|
43
|
+
assert_equal(1, scheduler.jobs.size)
|
44
|
+
assert_equal(Moment::Scheduler::Event.new(job, simple),
|
45
|
+
scheduler.unschedule(job_id))
|
46
|
+
assert_equal(0, scheduler.jobs.size)
|
47
|
+
end
|
48
|
+
|
49
|
+
def test_cleanup
|
50
|
+
scheduler = Moment::Scheduler.new
|
51
|
+
dead = Moment::SimpleTrigger.new(nil, Time.now-2, 1)
|
52
|
+
alive = Moment::SimpleTrigger.new(nil, nil, 1)
|
53
|
+
|
54
|
+
scheduler.schedule(Moment::Job.new, dead)
|
55
|
+
scheduler.schedule(Moment::Job.new, alive)
|
56
|
+
|
57
|
+
scheduler.clean_up
|
58
|
+
|
59
|
+
assert_equal(1, scheduler.jobs.size)
|
60
|
+
assert_equal(alive, scheduler.jobs.first[:trigger])
|
61
|
+
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
data/test/server_test.rb
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
|
2
|
+
require 'test/unit'
|
3
|
+
require 'stringio'
|
4
|
+
require File.dirname(__FILE__) + '/../lib/server'
|
5
|
+
require File.dirname(__FILE__) + '/../lib/simple_trigger'
|
6
|
+
require File.dirname(__FILE__) + '/../lib/job'
|
7
|
+
|
8
|
+
class ServerTest < Test::Unit::TestCase
|
9
|
+
|
10
|
+
def test_initialize
|
11
|
+
server = Moment::Server.new
|
12
|
+
assert_equal([], server.jobs)
|
13
|
+
end
|
14
|
+
|
15
|
+
class CountExecuted < Moment::Job
|
16
|
+
attr_reader :executed
|
17
|
+
def initialize
|
18
|
+
@executed = 0
|
19
|
+
end
|
20
|
+
def execute
|
21
|
+
@executed += 1
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def test_threading
|
26
|
+
scheduler = Moment::Server.new
|
27
|
+
simple = Moment::SimpleTrigger.new(Time.now, nil, 1)
|
28
|
+
job = CountExecuted.new
|
29
|
+
|
30
|
+
scheduler.run # start running first
|
31
|
+
scheduler.schedule(job, simple)
|
32
|
+
sleep 3 # make sure to wait for the job to run at least once
|
33
|
+
|
34
|
+
assert_not_equal(0, job.executed)
|
35
|
+
end
|
36
|
+
|
37
|
+
def test_serialization
|
38
|
+
scheduler = Moment::Server.new
|
39
|
+
scheduler.schedule(CountExecuted.new, Moment::SimpleTrigger.new(nil, nil, 1))
|
40
|
+
file = StringIO.new
|
41
|
+
scheduler.dump(file)
|
42
|
+
assert_not_equal(0, file.size)
|
43
|
+
old_jobs = scheduler.jobs
|
44
|
+
assert_not_equal(0, scheduler.jobs.size)
|
45
|
+
|
46
|
+
file.rewind
|
47
|
+
scheduler = Moment::Server.new
|
48
|
+
assert_equal(0, scheduler.jobs.size)
|
49
|
+
scheduler.load(file)
|
50
|
+
assert_equal(old_jobs.size, scheduler.jobs.size)
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
|
2
|
+
require 'test/unit'
|
3
|
+
require File.dirname(__FILE__) + '/../lib/shell_job'
|
4
|
+
|
5
|
+
class ShellJobTest < Test::Unit::TestCase
|
6
|
+
|
7
|
+
def test_initialize
|
8
|
+
job = Moment::ShellJob.new('cmd')
|
9
|
+
assert_not_nil(job)
|
10
|
+
assert_equal('cmd', job.command)
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_execute
|
14
|
+
job = Moment::ShellJob.new('ls')
|
15
|
+
job.execute
|
16
|
+
assert_equal(`ls`, job.last_result)
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
|
2
|
+
require 'test/unit'
|
3
|
+
require File.dirname(__FILE__) + '/../lib/simple_trigger'
|
4
|
+
|
5
|
+
class SimpleTriggerTest < Test::Unit::TestCase
|
6
|
+
|
7
|
+
def test_create
|
8
|
+
trig = Moment::SimpleTrigger.new
|
9
|
+
assert_not_nil(trig)
|
10
|
+
end
|
11
|
+
|
12
|
+
def test_start_time
|
13
|
+
trig = Moment::SimpleTrigger.new(nil, Time.now, nil)
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
metadata
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
rubygems_version: 0.8.11
|
3
|
+
specification_version: 1
|
4
|
+
name: moment
|
5
|
+
version: !ruby/object:Gem::Version
|
6
|
+
version: 0.1.0
|
7
|
+
date: 2006-04-25 00:00:00 -07:00
|
8
|
+
summary: A scheduler (like cron) that will run specified ruby code at given intervals or times
|
9
|
+
require_paths:
|
10
|
+
- lib
|
11
|
+
email: visnux@swivel.com
|
12
|
+
homepage: http://visnup.com
|
13
|
+
rubyforge_project:
|
14
|
+
description:
|
15
|
+
autorequire: moment
|
16
|
+
default_executable:
|
17
|
+
bindir: bin
|
18
|
+
has_rdoc: true
|
19
|
+
required_ruby_version: !ruby/object:Gem::Version::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">"
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 0.0.0
|
24
|
+
version:
|
25
|
+
platform: ruby
|
26
|
+
signing_key:
|
27
|
+
cert_chain:
|
28
|
+
authors:
|
29
|
+
- Visnu Pitiyanuvath
|
30
|
+
files:
|
31
|
+
- bin/moment_server
|
32
|
+
- lib/server.rb
|
33
|
+
- lib/client.rb
|
34
|
+
- lib/trigger.rb
|
35
|
+
- lib/shell_job.rb
|
36
|
+
- lib/moment.rb
|
37
|
+
- lib/cron_trigger.rb
|
38
|
+
- lib/simple_trigger.rb
|
39
|
+
- lib/scheduler.rb
|
40
|
+
- lib/job.rb
|
41
|
+
- test/server_test.rb
|
42
|
+
- test/simple_trigger_test.rb
|
43
|
+
- test/scheduler_test.rb
|
44
|
+
- test/job_test.rb
|
45
|
+
- test/cron_trigger_test.rb
|
46
|
+
- test/trigger_test.rb
|
47
|
+
- test/shell_job_test.rb
|
48
|
+
- test/moment_test.rb
|
49
|
+
- README
|
50
|
+
test_files:
|
51
|
+
- test/moment_test.rb
|
52
|
+
rdoc_options: []
|
53
|
+
|
54
|
+
extra_rdoc_files:
|
55
|
+
- README
|
56
|
+
- bin/moment_server
|
57
|
+
executables:
|
58
|
+
- moment_server
|
59
|
+
extensions: []
|
60
|
+
|
61
|
+
requirements: []
|
62
|
+
|
63
|
+
dependencies: []
|
64
|
+
|