moment 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/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
|
+
|