crontab 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +17 -0
- data/Rakefile +23 -0
- data/crontab.gemspec +22 -0
- data/lib/crontab.rb +34 -0
- data/lib/crontab/entry.rb +49 -0
- data/lib/crontab/schedule.rb +244 -0
- data/spec/crontab/entry_spec.rb +128 -0
- data/spec/crontab/schedule_spec.rb +356 -0
- data/spec/crontab_spec.rb +37 -0
- metadata +75 -0
data/README.rdoc
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
= What is this?
|
2
|
+
|
3
|
+
A crontab(5) parser.
|
4
|
+
|
5
|
+
== Usage
|
6
|
+
|
7
|
+
crontab = Crontab.parse(src)
|
8
|
+
|
9
|
+
from_date = Date.parse('2009-10-10')
|
10
|
+
to_date = Date.parse('2009-10-20')
|
11
|
+
|
12
|
+
crontab.entries.each do |e|
|
13
|
+
puts e.command
|
14
|
+
e.schedule.from(from_date).until(to_date) do |timing|
|
15
|
+
puts timing
|
16
|
+
end
|
17
|
+
end
|
data/Rakefile
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'rake/gempackagetask'
|
2
|
+
require 'rake/rdoctask'
|
3
|
+
require 'spec/rake/spectask'
|
4
|
+
|
5
|
+
task :default => :spec
|
6
|
+
|
7
|
+
spec = Gem::Specification.load('crontab.gemspec')
|
8
|
+
|
9
|
+
Rake::GemPackageTask.new(spec) do |pkg|
|
10
|
+
pkg.need_zip = true
|
11
|
+
pkg.need_tar_bz2 = true
|
12
|
+
end
|
13
|
+
|
14
|
+
Rake::RDocTask.new(:rdoc) do |rdoc|
|
15
|
+
rdoc.main = 'README.rdoc'
|
16
|
+
rdoc.rdoc_files.include('README.rdoc', 'lib/**/*.rb')
|
17
|
+
rdoc.options << '--all'
|
18
|
+
end
|
19
|
+
|
20
|
+
Spec::Rake::SpecTask.new do |t|
|
21
|
+
t.warning = true
|
22
|
+
t.rcov = false
|
23
|
+
end
|
data/crontab.gemspec
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = 'crontab'
|
3
|
+
s.version = '0.0.2'
|
4
|
+
s.author = 'OZAWA Sakuro'
|
5
|
+
s.email = 'github@2238club.org'
|
6
|
+
s.homepage = 'http://github.com/sakuro/crontab'
|
7
|
+
s.platform = Gem::Platform::RUBY
|
8
|
+
s.summary = 'A crontab(5) entry parser'
|
9
|
+
s.files = [
|
10
|
+
'lib/crontab.rb',
|
11
|
+
'lib/crontab/entry.rb',
|
12
|
+
'lib/crontab/schedule.rb',
|
13
|
+
'spec/crontab_spec.rb',
|
14
|
+
'spec/crontab/entry_spec.rb',
|
15
|
+
'spec/crontab/schedule_spec.rb',
|
16
|
+
'crontab.gemspec',
|
17
|
+
'Rakefile',
|
18
|
+
'README.rdoc'
|
19
|
+
]
|
20
|
+
s.has_rdoc = true
|
21
|
+
s.rubyforge_project = 'n/a'
|
22
|
+
end
|
data/lib/crontab.rb
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'crontab/schedule'
|
2
|
+
require 'crontab/entry'
|
3
|
+
|
4
|
+
# A class which represents crontab(5) content.
|
5
|
+
class Crontab
|
6
|
+
class << self
|
7
|
+
# Parses a crontab(5) text.
|
8
|
+
#
|
9
|
+
# * <tt>src</tt> string in crontab(5) format
|
10
|
+
def parse(src)
|
11
|
+
entries = []
|
12
|
+
env = {}
|
13
|
+
src.lines.each do |line|
|
14
|
+
line.strip!
|
15
|
+
case line
|
16
|
+
when /\A[\d*]/
|
17
|
+
entries << Crontab::Entry.parse(line)
|
18
|
+
when /\A([A-Z][A-Z0-9_]*)\s*=\s*/, /\A"([A-Z][A-Z0-9_]*)"\s*=\s*/, /\A'([A-Z][A-Z0-9_]*)'\s*=\s*/
|
19
|
+
name = $1
|
20
|
+
value = $'
|
21
|
+
value = $1 if /\A'(.*)'\z/ =~ value or /\A"(.*)"\z/ =~ value
|
22
|
+
env[name] = value
|
23
|
+
end
|
24
|
+
end
|
25
|
+
new(entries, env)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def initialize(entries, env)
|
30
|
+
@entries = entries.dup.freeze
|
31
|
+
@env = env.dup.freeze
|
32
|
+
end
|
33
|
+
attr_reader :entries, :env
|
34
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
class Crontab
|
2
|
+
# A class which represents a job line in crontab(5).
|
3
|
+
class Entry
|
4
|
+
# Creates a crontab(5) entry.
|
5
|
+
#
|
6
|
+
# * <tt>schedule</tt> A Crontab::Schedule instance.
|
7
|
+
# * <tt>command</tt>
|
8
|
+
# * <tt>uid</tt>
|
9
|
+
def initialize(schedule, command, uid=nil)
|
10
|
+
raise ArgumentError, 'invalid schedule' unless schedule.is_a? Schedule
|
11
|
+
raise ArgumentError, 'invalid command' unless command.is_a? String
|
12
|
+
|
13
|
+
@schedule = schedule.freeze
|
14
|
+
@command = command.freeze
|
15
|
+
@uid =
|
16
|
+
case uid
|
17
|
+
when String
|
18
|
+
Etc.getpwnam(uid).uid
|
19
|
+
when Integer
|
20
|
+
uid
|
21
|
+
when nil
|
22
|
+
Process.uid
|
23
|
+
else
|
24
|
+
raise ArgumentError, 'invalid uid'
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
attr_reader :schedule, :command, :uid
|
29
|
+
class << self
|
30
|
+
# Parses a string line in crontab(5) job format.
|
31
|
+
#
|
32
|
+
# * <tt>options[:system]</tt> when true system wide crontab is assumed
|
33
|
+
# and <tt>@uid</tt> is extracted from <i>line</i>.
|
34
|
+
def parse(line, options={})
|
35
|
+
options = { :system => false }.merge(options)
|
36
|
+
line = line.strip
|
37
|
+
number_of_fields = 1
|
38
|
+
number_of_fields += line.start_with?('@') ? 1 : 5
|
39
|
+
number_of_fields += 1 if options[:system]
|
40
|
+
words = line.split(/\s+/, number_of_fields)
|
41
|
+
command = words.pop
|
42
|
+
uid = options[:system] ? words.pop : Process.uid
|
43
|
+
spec = words.join(' ')
|
44
|
+
schedule = Crontab::Schedule.new(spec)
|
45
|
+
new(schedule, command, uid)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,244 @@
|
|
1
|
+
require 'date'
|
2
|
+
|
3
|
+
class Crontab
|
4
|
+
|
5
|
+
# A class which represents schedules in crontab(5).
|
6
|
+
class Schedule
|
7
|
+
|
8
|
+
MONTH_NAMES = %w(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec)
|
9
|
+
DAY_OF_WEEK_NAMES = %w(Sun Mon Tue Wed Thu Fri Sat)
|
10
|
+
DAYS_IN_MONTH = [nil, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
|
11
|
+
|
12
|
+
# Creates a cron job schedule.
|
13
|
+
#
|
14
|
+
# * <tt>spec</tt> schedule specifier defined in crontab(5).
|
15
|
+
# Both numeric and named spec are supported.
|
16
|
+
# * <tt>start</tt> the time *or* date this schedule begins.
|
17
|
+
#
|
18
|
+
# Supported named specs are:
|
19
|
+
# * <tt>@yearly</tt>, <tt>@annually</tt>
|
20
|
+
# * <tt>@monthly</tt>
|
21
|
+
# * <tt>@weekly</tt>
|
22
|
+
# * <tt>@daily</tt>, <tt>@midnight</tt>
|
23
|
+
# * <tt>@hourly</tt>
|
24
|
+
def initialize(spec, start=Time.now)
|
25
|
+
raise ArgumentError, 'empty spec' if spec == '' or spec.nil?
|
26
|
+
@start = ensure_time(start)
|
27
|
+
spec = spec.strip
|
28
|
+
if spec.start_with?('@')
|
29
|
+
parse_symbolic_spec(spec)
|
30
|
+
else
|
31
|
+
parse_spec(spec)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def ==(other)
|
36
|
+
self.minutes == other.minutes &&
|
37
|
+
self.hours == other.hours &&
|
38
|
+
self.day_of_months == other.day_of_months &&
|
39
|
+
self.months == other.months &&
|
40
|
+
self.day_of_months_given? == other.day_of_months_given? &&
|
41
|
+
self.day_of_weeks == other.day_of_weeks &&
|
42
|
+
self.day_of_weeks_given? == other.day_of_weeks_given? &&
|
43
|
+
self.start == other.start
|
44
|
+
end
|
45
|
+
|
46
|
+
def hash
|
47
|
+
[ minutes, hours, day_of_months, months, day_of_weeks, day_of_months_given?, day_of_weeks_given?, start ].map(&:hash).inject(:^)
|
48
|
+
end
|
49
|
+
|
50
|
+
attr_reader :minutes, :hours, :day_of_months, :months, :day_of_weeks, :start
|
51
|
+
|
52
|
+
# Changes the start timing of this schedule to <i>time_or_date</i>.
|
53
|
+
def start=(time_or_date)
|
54
|
+
@start = ensure_time(time_or_date)
|
55
|
+
end
|
56
|
+
|
57
|
+
# Creates new schedule which starts at the given <i>time_or_date</i>.
|
58
|
+
def from(time_or_date)
|
59
|
+
self.dup.tap {|new_schedule| new_schedule.start = ensure_time(time_or_date) }
|
60
|
+
end
|
61
|
+
|
62
|
+
def day_of_months_given?
|
63
|
+
!!@day_of_months_given # ensures boolean
|
64
|
+
end
|
65
|
+
protected :day_of_months_given?
|
66
|
+
|
67
|
+
def day_of_weeks_given?
|
68
|
+
!!@day_of_weeks_given # ensures boolean
|
69
|
+
end
|
70
|
+
protected :day_of_weeks_given?
|
71
|
+
|
72
|
+
# Iterates over timings specified in this schedule, starting at <tt>@start</tt>.
|
73
|
+
def each
|
74
|
+
return to_enum unless block_given?
|
75
|
+
year = @start.year
|
76
|
+
seeking = Hash.new {|h,k| h[k] = true }
|
77
|
+
loop do
|
78
|
+
@months.each do |month|
|
79
|
+
next if seeking[:month] and month < @start.month and @start.month <= @months.max
|
80
|
+
seeking[:month] = false
|
81
|
+
days = matching_days(year, month)
|
82
|
+
days.each do |day_of_month|
|
83
|
+
next if seeking[:day_of_month] and day_of_month < @start.day and @start.day <= days.max
|
84
|
+
seeking[:day_of_month] = false
|
85
|
+
@hours.each do |hour|
|
86
|
+
next if seeking[:hour] and hour < @start.hour and @start.hour <= @hours.max
|
87
|
+
seeking[:hour] = false
|
88
|
+
@minutes.each do |minute|
|
89
|
+
begin
|
90
|
+
t = Time.local(year, month, day_of_month, hour, minute)
|
91
|
+
rescue ArgumentError
|
92
|
+
raise StopIteration
|
93
|
+
end
|
94
|
+
yield(t) if @start <= t
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
year += 1
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
include Enumerable
|
104
|
+
|
105
|
+
# Iterates over timings from <tt>@start</tt> until given <i>time_or_date</i>.
|
106
|
+
def until(time_or_date)
|
107
|
+
time = ensure_time(time_or_date)
|
108
|
+
if block_given?
|
109
|
+
each do |t|
|
110
|
+
break if time < t
|
111
|
+
yield(t)
|
112
|
+
end
|
113
|
+
else
|
114
|
+
inject([]) do |timings, t|
|
115
|
+
break timings if time < t
|
116
|
+
timings << t
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
private
|
122
|
+
|
123
|
+
def parse_spec(spec)
|
124
|
+
args = spec.split(/\s+/)
|
125
|
+
raise ArgumentError, 'wrong number of spec fields: %d' % args.size unless args.size == 5
|
126
|
+
@minutes = parse_spec_field(args[0], 0..59, :accept_name => false)
|
127
|
+
@hours = parse_spec_field(args[1], 0..23, :accept_name => false)
|
128
|
+
@day_of_months = parse_spec_field(args[2], 1..31, :accept_name => false)
|
129
|
+
@day_of_months_given = args[2] != '*'
|
130
|
+
@months = parse_spec_field(args[3], 1..12, :names => MONTH_NAMES, :base => 1, :accept_name => true)
|
131
|
+
@day_of_weeks = parse_spec_field(args[4], 0..6, :names => DAY_OF_WEEK_NAMES, :base => 0, :accept_name => true, :allow_end => true)
|
132
|
+
@day_of_weeks_given = args[4] != '*'
|
133
|
+
end
|
134
|
+
|
135
|
+
def parse_symbolic_spec(spec)
|
136
|
+
case spec
|
137
|
+
when '@yearly', '@annually'
|
138
|
+
parse_spec('0 0 1 1 *')
|
139
|
+
when '@monthly'
|
140
|
+
parse_spec('0 0 1 * *')
|
141
|
+
when '@weekly'
|
142
|
+
parse_spec('0 0 * * 0')
|
143
|
+
when '@daily', '@midnight'
|
144
|
+
parse_spec('0 0 * * *')
|
145
|
+
when '@hourly'
|
146
|
+
parse_spec('0 * * * *')
|
147
|
+
when '@reboot'
|
148
|
+
raise NotImplementedError, '@reboot is not supported'
|
149
|
+
else
|
150
|
+
raise ArgumentError, 'unknown crontab spec: %s' % spec
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
def parse_spec_field(spec, range, options={})
|
155
|
+
options = { :accept_name => true, :allow_end => false }.merge(options)
|
156
|
+
case spec
|
157
|
+
when '*'
|
158
|
+
range.to_a
|
159
|
+
when /\A\d+\z/
|
160
|
+
[ parse_number(spec, range, options) ]
|
161
|
+
when /\A[a-z]{3}\z/i
|
162
|
+
[ parse_name(spec, range, options) ]
|
163
|
+
when /\A(\d+)-(\d+)\z/i
|
164
|
+
parse_range($1, $2, range, options.merge(:accept_name => false))
|
165
|
+
when /,/
|
166
|
+
parse_list(spec.split(/,/), range, options.merge(:accept_name => false))
|
167
|
+
when /\A(\*|\d+-\d+)\/(\d+)\z/
|
168
|
+
parse_step($1, $2.to_i, range, options.merge(:accept_name => false))
|
169
|
+
else
|
170
|
+
raise ArgumentError, 'wrong spec format: %s' % spec
|
171
|
+
end.tap do |result|
|
172
|
+
if options[:allow_end] && result.include?(range.first) && result.include?(range.last.succ)
|
173
|
+
result.delete(range.last.succ)
|
174
|
+
end
|
175
|
+
end.sort.uniq.freeze
|
176
|
+
end
|
177
|
+
|
178
|
+
def parse_number(spec, range, options)
|
179
|
+
v = spec.to_i
|
180
|
+
return v if range.include?(v) or options[:allow_end] && range.last.succ == v
|
181
|
+
raise ArgumentError, 'argument out of range: %s' % spec
|
182
|
+
end
|
183
|
+
|
184
|
+
def parse_name(spec, range, options)
|
185
|
+
raise ArgumentError, 'names not allowed in this field: %s' % spec unless options[:names] and options[:base]
|
186
|
+
raise ArgumentError, 'names not allowed in this context: %s' % spec unless options[:accept_name]
|
187
|
+
v = options[:names].index {|name| name.downcase == spec.downcase }
|
188
|
+
base = options[:base]
|
189
|
+
return v + base if v && range.include?(v + base)
|
190
|
+
raise ArgumentError, 'argument out of range: %s' % spec
|
191
|
+
end
|
192
|
+
|
193
|
+
def parse_range(from, to, range, options)
|
194
|
+
from = parse_number(from, range, options[:allow_end])
|
195
|
+
to = parse_number(to, range, options[:allow_end])
|
196
|
+
raise ArgumentError, 'start is after or equal to end: %s' % spec unless from < to
|
197
|
+
(from..to).to_a
|
198
|
+
end
|
199
|
+
|
200
|
+
def parse_list(specs, range, options)
|
201
|
+
specs.map{|spec| parse_spec_field(spec, range, options) }.flatten
|
202
|
+
end
|
203
|
+
|
204
|
+
def parse_step(first, step, range, options)
|
205
|
+
v = parse_spec_field(first, range, options)
|
206
|
+
v.first.step(v.size == 1 ? range.last : v.last, step).to_a
|
207
|
+
end
|
208
|
+
|
209
|
+
def matching_days(year, month)
|
210
|
+
days = number_of_days(year, month)
|
211
|
+
(1..days).select do |day|
|
212
|
+
wday = day_of_week(year, month, day)
|
213
|
+
if day_of_months_given?
|
214
|
+
if day_of_weeks_given?
|
215
|
+
@day_of_months.include?(day) or @day_of_weeks.include?(wday)
|
216
|
+
else
|
217
|
+
@day_of_months.include?(day)
|
218
|
+
end
|
219
|
+
else
|
220
|
+
if day_of_weeks_given?
|
221
|
+
@day_of_weeks.include?(wday)
|
222
|
+
else
|
223
|
+
true
|
224
|
+
end
|
225
|
+
end
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
def ensure_time(time_or_date)
|
230
|
+
time_or_date.is_a?(Date) ? Time.local(time_or_date.year, time_or_date.month, time_or_date.day) : time_or_date
|
231
|
+
end
|
232
|
+
|
233
|
+
def number_of_days(year, month)
|
234
|
+
days = DAYS_IN_MONTH[month]
|
235
|
+
days += 1 if month == 2 && (year % 4 == 0 && year % 100 != 0 || year % 400 == 0)
|
236
|
+
days
|
237
|
+
end
|
238
|
+
|
239
|
+
def day_of_week(year, month, day)
|
240
|
+
Date.new(year, month, day).wday
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
end
|
@@ -0,0 +1,128 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), '..', 'spec_helper')
|
2
|
+
require 'crontab/entry'
|
3
|
+
require 'crontab/schedule'
|
4
|
+
|
5
|
+
describe Crontab::Entry do
|
6
|
+
describe 'when instantiating' do
|
7
|
+
before :each do
|
8
|
+
@schedule = Crontab::Schedule.new('0 0 * * *') # @daily
|
9
|
+
@command = 'echo hello'
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'should accpet Crontab::Schedule and command String' do
|
13
|
+
entry = Crontab::Entry.new(@schedule, @command)
|
14
|
+
entry.uid.should == Process.uid
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'should accpet Crontab::Schedule, command String and user String' do
|
18
|
+
uid = Process.uid
|
19
|
+
user = Etc.getpwuid(uid)
|
20
|
+
entry = Crontab::Entry.new(@schedule, @command, user.name)
|
21
|
+
entry.uid.should == uid
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'should accpet Crontab::Schedule, command String and user ID' do
|
25
|
+
uid = Process.uid
|
26
|
+
entry = Crontab::Entry.new(@schedule, @command, uid)
|
27
|
+
entry.uid.should == uid
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'should reject invalid arguments' do
|
31
|
+
lambda { Crontab::Entry.new(@schedule, nil) }.should raise_error(ArgumentError)
|
32
|
+
lambda { Crontab::Entry.new(@schedule, Object.new) }.should raise_error(ArgumentError)
|
33
|
+
lambda { Crontab::Entry.new(@schedule) }.should raise_error(ArgumentError)
|
34
|
+
lambda { Crontab::Entry.new(nil, @command) }.should raise_error(ArgumentError)
|
35
|
+
lambda { Crontab::Entry.new(Object.new, @command) }.should raise_error(ArgumentError)
|
36
|
+
lambda { Crontab::Entry.new(@command) }.should raise_error(ArgumentError)
|
37
|
+
lambda { Crontab::Entry.new }.should raise_error(ArgumentError)
|
38
|
+
lambda { Crontab::Entry.new(nil) }.should raise_error(ArgumentError)
|
39
|
+
end
|
40
|
+
|
41
|
+
describe 'by parsing' do
|
42
|
+
it 'should parse crontab entry line' do
|
43
|
+
entry = Crontab::Entry.parse('0 0 * * * echo hello')
|
44
|
+
entry.schedule.from(@schedule.start).should == @schedule and
|
45
|
+
entry.command.should == @command and
|
46
|
+
entry.uid.should == Process.uid
|
47
|
+
|
48
|
+
entry = Crontab::Entry.parse('0 0 * * * echo hello', :system => false)
|
49
|
+
entry.schedule.from(@schedule.start).should == @schedule and
|
50
|
+
entry.command.should == @command and
|
51
|
+
entry.uid.should == Process.uid
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'should parse crontab entry line with symbolic schedule' do
|
55
|
+
entry = Crontab::Entry.parse('@daily echo hello')
|
56
|
+
entry.schedule.from(@schedule.start).should == @schedule and
|
57
|
+
entry.command.should == @command and
|
58
|
+
entry.uid.should == Process.uid
|
59
|
+
|
60
|
+
entry = Crontab::Entry.parse('0 0 * * * echo hello', :system => false)
|
61
|
+
entry.schedule.from(@schedule.start).should == @schedule and
|
62
|
+
entry.command.should == @command and
|
63
|
+
entry.uid.should == Process.uid
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'should parse system crontab entry line' do
|
67
|
+
entry = Crontab::Entry.parse('0 0 * * * root echo hello', :system => true)
|
68
|
+
entry.schedule.from(@schedule.start).should == @schedule and
|
69
|
+
entry.command.should == @command and
|
70
|
+
entry.uid.should == Etc.getpwnam('root').uid
|
71
|
+
end
|
72
|
+
|
73
|
+
it 'should parse system crontab entry line with symbolic schedule' do
|
74
|
+
entry = Crontab::Entry.parse('@daily root echo hello', :system => true)
|
75
|
+
entry.schedule.from(@schedule.start).should == @schedule and
|
76
|
+
entry.command.should == @command and
|
77
|
+
entry.uid.should == Etc.getpwnam('root').uid
|
78
|
+
end
|
79
|
+
|
80
|
+
it 'should ignore leading whitespaces' do
|
81
|
+
entry = Crontab::Entry.parse(' @daily echo hello')
|
82
|
+
entry.schedule.from(@schedule.start).should == @schedule and
|
83
|
+
entry.command.should == @command
|
84
|
+
|
85
|
+
entry = Crontab::Entry.parse(' 0 0 * * * echo hello')
|
86
|
+
entry.schedule.from(@schedule.start).should == @schedule and
|
87
|
+
entry.command.should == @command
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
describe 'when accessing' do
|
93
|
+
before :each do
|
94
|
+
@schedule = Crontab::Schedule.new('@hourly')
|
95
|
+
@other_schedule = Crontab::Schedule.new('@monthly')
|
96
|
+
@command = 'ehco hello'
|
97
|
+
@other_command = 'echo bonjour'
|
98
|
+
@entry = Crontab::Entry.new(@schedule, @command)
|
99
|
+
end
|
100
|
+
|
101
|
+
it 'should be read-accessible to schedule' do
|
102
|
+
@entry.schedule.should == @schedule and
|
103
|
+
lambda { @entry.schedule = @other_schedule }.should raise_error(NoMethodError)
|
104
|
+
@entry.schedule.should == @schedule
|
105
|
+
end
|
106
|
+
|
107
|
+
it 'should freeze schedule' do
|
108
|
+
@entry.schedule.should be_frozen
|
109
|
+
end
|
110
|
+
|
111
|
+
it 'should be read-accessible to command' do
|
112
|
+
@entry.command.should == @command and
|
113
|
+
lambda { @entry.command = @other_command }.should raise_error(NoMethodError)
|
114
|
+
@entry.command.should == @command
|
115
|
+
end
|
116
|
+
|
117
|
+
it 'should freeze command' do
|
118
|
+
@entry.command.should be_frozen
|
119
|
+
end
|
120
|
+
|
121
|
+
it 'should be read-accessible to uid' do
|
122
|
+
@entry.uid.should == Process.uid and
|
123
|
+
Process.uid.should_not == 0 and
|
124
|
+
lambda { @entry.uid = 0 }.should raise_error(NoMethodError)
|
125
|
+
@entry.uid.should == Process.uid
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
@@ -0,0 +1,356 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), '..', 'spec_helper')
|
2
|
+
require 'crontab/schedule'
|
3
|
+
|
4
|
+
describe Crontab::Schedule do
|
5
|
+
describe 'when parsing spec' do
|
6
|
+
it 'should accept spec with asterisks' do
|
7
|
+
schedule = Crontab::Schedule.new('* * * * *')
|
8
|
+
schedule.minutes.should == (0..59).to_a and
|
9
|
+
schedule.hours.should == (0..23).to_a and
|
10
|
+
schedule.day_of_months.should == (1..31).to_a and
|
11
|
+
schedule.months.should == (1..12).to_a and
|
12
|
+
schedule.day_of_weeks.should == (0..6).to_a
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'should accept spec with simple numbers' do
|
16
|
+
schedule = Crontab::Schedule.new('0 0 1 1 0')
|
17
|
+
schedule.minutes.should == [0] and
|
18
|
+
schedule.hours.should == [0] and
|
19
|
+
schedule.day_of_months.should == [1] and
|
20
|
+
schedule.months.should == [1] and
|
21
|
+
schedule.day_of_weeks.should == [0]
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'should accept spec with names(Jan, Sun etc.)' do
|
25
|
+
schedule = Crontab::Schedule.new('0 0 1 Jan sUn')
|
26
|
+
schedule.minutes.should == [0] and
|
27
|
+
schedule.hours.should == [0] and
|
28
|
+
schedule.day_of_months.should == [1] and
|
29
|
+
schedule.months.should == [1] and
|
30
|
+
schedule.day_of_weeks.should == [0]
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'should accept spec with ranges(N-N)' do
|
34
|
+
schedule = Crontab::Schedule.new('0-1 0-1 1-2 1-2 0-1')
|
35
|
+
schedule.minutes.should == [0, 1] and
|
36
|
+
schedule.hours.should == [0, 1] and
|
37
|
+
schedule.day_of_months.should == [1, 2] and
|
38
|
+
schedule.months.should == [1, 2] and
|
39
|
+
schedule.day_of_weeks.should == [0, 1]
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'should accept spec with lists(comma separaged ranges/numbers)' do
|
43
|
+
schedule = Crontab::Schedule.new('0,10,20 0,10,20 1,11,21 1,3,5 0,4,5,6')
|
44
|
+
schedule.minutes.should == [0, 10, 20] and
|
45
|
+
schedule.hours.should == [0, 10, 20] and
|
46
|
+
schedule.day_of_months.should == [1, 11, 21] and
|
47
|
+
schedule.months.should == [1, 3, 5] and
|
48
|
+
schedule.day_of_weeks.should == [0, 4, 5, 6]
|
49
|
+
|
50
|
+
schedule = Crontab::Schedule.new('0,1-5,10 0,1-5,10 1,2-5,10 1,2-4,10 0,1-3,7')
|
51
|
+
schedule.minutes.should == [0, 1, 2, 3, 4, 5, 10] and
|
52
|
+
schedule.hours.should == [0, 1, 2, 3, 4, 5, 10] and
|
53
|
+
schedule.day_of_months.should == [1, 2, 3, 4, 5, 10] and
|
54
|
+
schedule.months.should == [1, 2, 3, 4, 10] and
|
55
|
+
schedule.day_of_weeks.should == [0, 1, 2, 3]
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'should accept spec with steps(range/STEP or */STEP)' do
|
59
|
+
schedule = Crontab::Schedule.new('0-30/10 1-20/5 5-30/7 4-10/2 1-5/2')
|
60
|
+
schedule.minutes.should == [0, 10, 20, 30] and
|
61
|
+
schedule.hours.should == [1, 6, 11, 16 ] and
|
62
|
+
schedule.day_of_months.should == [5, 12, 19, 26 ] and
|
63
|
+
schedule.months.should == [4, 6, 8, 10] and
|
64
|
+
schedule.day_of_weeks.should == [1, 3, 5]
|
65
|
+
|
66
|
+
schedule = Crontab::Schedule.new('*/25 */5 */10 */4 */2')
|
67
|
+
schedule.minutes.should == [0, 25, 50] and
|
68
|
+
schedule.hours.should == [0, 5, 10, 15, 20] and
|
69
|
+
schedule.day_of_months.should == [1, 11, 21, 31] and
|
70
|
+
schedule.months.should == [1, 5, 9] and
|
71
|
+
schedule.day_of_weeks.should == [0, 2, 4, 6]
|
72
|
+
end
|
73
|
+
|
74
|
+
it 'should reject spec with incorrect number of fields' do
|
75
|
+
lambda { Crontab::Schedule.new }.should raise_error(ArgumentError)
|
76
|
+
lambda { Crontab::Schedule.new(nil) }.should raise_error(ArgumentError)
|
77
|
+
lambda { Crontab::Schedule.new('') }.should raise_error(ArgumentError)
|
78
|
+
lambda { Crontab::Schedule.new('x') }.should raise_error(ArgumentError)
|
79
|
+
lambda { Crontab::Schedule.new('* * * *') }.should raise_error(ArgumentError)
|
80
|
+
lambda { Crontab::Schedule.new('* * * * * *') }.should raise_error(ArgumentError)
|
81
|
+
end
|
82
|
+
|
83
|
+
it 'should reject spec with incorrect values' do
|
84
|
+
lambda { Crontab::Schedule.new('x 0 1 0 0') }.should raise_error(ArgumentError)
|
85
|
+
lambda { Crontab::Schedule.new('-1 0 1 1 0') }.should raise_error(ArgumentError)
|
86
|
+
lambda { Crontab::Schedule.new('60 0 1 1 0') }.should raise_error(ArgumentError)
|
87
|
+
lambda { Crontab::Schedule.new('0 24 1 1 0') }.should raise_error(ArgumentError)
|
88
|
+
lambda { Crontab::Schedule.new('0 0 32 1 0') }.should raise_error(ArgumentError)
|
89
|
+
lambda { Crontab::Schedule.new('0 0 1 13 0') }.should raise_error(ArgumentError)
|
90
|
+
lambda { Crontab::Schedule.new('0 0 1 1 8') }.should raise_error(ArgumentError)
|
91
|
+
lambda { Crontab::Schedule.new('0 0 1 Mon 8') }.should raise_error(ArgumentError)
|
92
|
+
lambda { Crontab::Schedule.new('Jan Sun 1 1 8') }.should raise_error(ArgumentError)
|
93
|
+
end
|
94
|
+
|
95
|
+
it 'should reject spec with names in lists and ranges' do
|
96
|
+
lambda { Crontab::Schedule.new('0 0 1 Jan-Feb *') }.should raise_error(ArgumentError)
|
97
|
+
lambda { Crontab::Schedule.new('0 0 1 Jan,Feb *') }.should raise_error(ArgumentError)
|
98
|
+
lambda { Crontab::Schedule.new('0 0 1 * Sun-Fri') }.should raise_error(ArgumentError)
|
99
|
+
lambda { Crontab::Schedule.new('0 0 1 * Sun,Fri') }.should raise_error(ArgumentError)
|
100
|
+
end
|
101
|
+
|
102
|
+
it 'should reject spec with simple numbers with step' do
|
103
|
+
lambda { Crontab::Schedule.new('0/2 0 1 1 0') }.should raise_error(ArgumentError)
|
104
|
+
lambda { Crontab::Schedule.new('0 0/2 1 1 0') }.should raise_error(ArgumentError)
|
105
|
+
lambda { Crontab::Schedule.new('0 0 1/2 1 0') }.should raise_error(ArgumentError)
|
106
|
+
lambda { Crontab::Schedule.new('0 0 1 1/2 0') }.should raise_error(ArgumentError)
|
107
|
+
lambda { Crontab::Schedule.new('0 0 1 1 0/2') }.should raise_error(ArgumentError)
|
108
|
+
end
|
109
|
+
|
110
|
+
it 'should accept symblic specs' do
|
111
|
+
start = Time.now
|
112
|
+
Crontab::Schedule.new('@yearly', start).should == Crontab::Schedule.new('0 0 1 1 *', start) and
|
113
|
+
Crontab::Schedule.new('@annually', start).should == Crontab::Schedule.new('0 0 1 1 *', start) and
|
114
|
+
Crontab::Schedule.new('@monthly', start).should == Crontab::Schedule.new('0 0 1 * *', start) and
|
115
|
+
Crontab::Schedule.new('@weekly', start).should == Crontab::Schedule.new('0 0 * * 0', start) and
|
116
|
+
Crontab::Schedule.new('@daily', start).should == Crontab::Schedule.new('0 0 * * *', start) and
|
117
|
+
Crontab::Schedule.new('@midnight', start).should == Crontab::Schedule.new('0 0 * * *', start) and
|
118
|
+
Crontab::Schedule.new('@hourly', start).should == Crontab::Schedule.new('0 * * * *', start)
|
119
|
+
end
|
120
|
+
|
121
|
+
it 'should reject @reboot' do
|
122
|
+
lambda { Crontab::Schedule.new('@reboot') }.should raise_error(NotImplementedError)
|
123
|
+
end
|
124
|
+
|
125
|
+
it 'should reject unknown symbolic spec' do
|
126
|
+
lambda { Crontab::Schedule.new('@unknown') }.should raise_error(ArgumentError)
|
127
|
+
end
|
128
|
+
|
129
|
+
it 'should ignore leading/trailing whitespaces' do
|
130
|
+
lambda {
|
131
|
+
Crontab::Schedule.new(' @daily')
|
132
|
+
Crontab::Schedule.new('@daily ')
|
133
|
+
Crontab::Schedule.new(' * * * * *')
|
134
|
+
Crontab::Schedule.new('* * * * * ')
|
135
|
+
}.should_not raise_error
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
describe 'when accessing' do
|
140
|
+
it 'should accept Time as start parameter' do
|
141
|
+
start = Time.local(2009, 3, 25, 10, 20, 30)
|
142
|
+
lambda {
|
143
|
+
schedule = Crontab::Schedule.new('* * * * *', start)
|
144
|
+
schedule.start.should == start
|
145
|
+
}.should_not raise_error
|
146
|
+
end
|
147
|
+
|
148
|
+
it 'should accept Date as start parameter' do
|
149
|
+
start = Date.new(2009, 3, 25)
|
150
|
+
lambda {
|
151
|
+
schedule = Crontab::Schedule.new('* * * * *', start)
|
152
|
+
schedule.start.should == Time.local(start.year, start.month, start.day)
|
153
|
+
}.should_not raise_error
|
154
|
+
end
|
155
|
+
|
156
|
+
it 'should set start to given Time' do
|
157
|
+
initial_start = Time.local(2009, 3, 25, 10, 20, 30)
|
158
|
+
schedule = Crontab::Schedule.new('* * * * *', initial_start)
|
159
|
+
schedule.start.should == initial_start and
|
160
|
+
|
161
|
+
new_start = Time.local(2009, 4, 25, 11, 21, 31)
|
162
|
+
schedule.start = new_start
|
163
|
+
schedule.start.should == new_start
|
164
|
+
end
|
165
|
+
|
166
|
+
it 'should set start to given Date' do
|
167
|
+
initial_start = Time.local(2009, 3, 25, 10, 20, 30)
|
168
|
+
schedule = Crontab::Schedule.new('* * * * *', initial_start)
|
169
|
+
schedule.start.should == initial_start and
|
170
|
+
|
171
|
+
new_start = Date.new(2009, 4, 25)
|
172
|
+
new_start_as_time = Time.local(new_start.year, new_start.month, new_start.day)
|
173
|
+
schedule.start = new_start
|
174
|
+
schedule.start.should == new_start_as_time
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
describe 'when duplicating' do
|
179
|
+
it 'should return a new schedule starting at given Time' do
|
180
|
+
initial_start = Time.local(2009, 3, 25, 10, 20, 30)
|
181
|
+
schedule = Crontab::Schedule.new('* * * * *', initial_start)
|
182
|
+
new_start = Time.local(2009, 4, 25, 11, 21, 31)
|
183
|
+
new_schedule = schedule.from(new_start)
|
184
|
+
|
185
|
+
new_schedule.object_id.should_not == schedule.object_id and
|
186
|
+
new_schedule.hash.should_not == schedule.hash and
|
187
|
+
new_schedule.should_not == schedule and
|
188
|
+
|
189
|
+
new_schedule.start.should == new_start and
|
190
|
+
new_schedule.minutes.should == schedule.minutes and
|
191
|
+
new_schedule.hours.should == schedule.hours and
|
192
|
+
new_schedule.day_of_months.should == schedule.day_of_months and
|
193
|
+
new_schedule.months.should == schedule.months and
|
194
|
+
new_schedule.day_of_weeks.should == schedule.day_of_weeks and
|
195
|
+
new_schedule.send(:day_of_months_given?).should == schedule.send(:day_of_months_given?) and
|
196
|
+
new_schedule.send(:day_of_weeks_given?).should == schedule.send(:day_of_weeks_given?)
|
197
|
+
end
|
198
|
+
|
199
|
+
it 'should return a new schedule starting at given Date' do
|
200
|
+
initial_start = Time.local(2009, 3, 25, 10, 20, 30)
|
201
|
+
schedule = Crontab::Schedule.new('* * * * *', initial_start)
|
202
|
+
new_start = Date.new(2009, 4, 25)
|
203
|
+
new_start_as_time = Time.local(new_start.year, new_start.month, new_start.day)
|
204
|
+
new_schedule = schedule.from(new_start)
|
205
|
+
|
206
|
+
new_schedule.object_id.should_not == schedule.object_id and
|
207
|
+
new_schedule.hash.should_not == schedule.hash and
|
208
|
+
new_schedule.should_not == schedule and
|
209
|
+
|
210
|
+
new_schedule.start.should == new_start_as_time and
|
211
|
+
new_schedule.minutes.should == schedule.minutes and
|
212
|
+
new_schedule.hours.should == schedule.hours and
|
213
|
+
new_schedule.day_of_months.should == schedule.day_of_months and
|
214
|
+
new_schedule.months.should == schedule.months and
|
215
|
+
new_schedule.day_of_weeks.should == schedule.day_of_weeks and
|
216
|
+
new_schedule.send(:day_of_months_given?).should == schedule.send(:day_of_months_given?) and
|
217
|
+
new_schedule.send(:day_of_weeks_given?).should == schedule.send(:day_of_weeks_given?)
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
describe 'when comparing' do
|
222
|
+
it 'should equal to other if and only if both are created from equivalent spec and start time' do
|
223
|
+
start = Time.now
|
224
|
+
Crontab::Schedule.new('0 0 1 1 0', start).should == Crontab::Schedule.new('0 0 1 1 0', start) and
|
225
|
+
Crontab::Schedule.new('0 0 1 1 0', start).should == Crontab::Schedule.new('0 0 1 Jan 0', start) and
|
226
|
+
Crontab::Schedule.new('0 0 1 1 0', start).should == Crontab::Schedule.new('0 0 1 1 Sun', start) and
|
227
|
+
|
228
|
+
Crontab::Schedule.new('0 0 1 1 0', start).should_not == Crontab::Schedule.new('1 0 1 1 0', start) and
|
229
|
+
Crontab::Schedule.new('0 0 1 1 0', start).should_not == Crontab::Schedule.new('0 1 1 1 0', start) and
|
230
|
+
Crontab::Schedule.new('0 0 1 1 0', start).should_not == Crontab::Schedule.new('0 0 2 1 0', start) and
|
231
|
+
Crontab::Schedule.new('0 0 1 1 0', start).should_not == Crontab::Schedule.new('0 0 1 2 0', start) and
|
232
|
+
Crontab::Schedule.new('0 0 1 1 0', start).should_not == Crontab::Schedule.new('0 0 1 1 1', start) and
|
233
|
+
|
234
|
+
Crontab::Schedule.new('0 0 1-5 1 0', start).should == Crontab::Schedule.new('0 0 1,2,3,4,5 1 0', start) and
|
235
|
+
Crontab::Schedule.new('0 0 1 1 0-4', start).should == Crontab::Schedule.new('0 0 1 1 0,1,2,3,4', start)
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
describe 'when enumerating' do
|
240
|
+
it 'should return given number of timings' do
|
241
|
+
schedule = Crontab::Schedule.new('* * * * *', Time.local(2009, 3, 8, 4, 30, 15))
|
242
|
+
expected = [
|
243
|
+
Time.local(2009, 3, 8, 4, 31),
|
244
|
+
Time.local(2009, 3, 8, 4, 32),
|
245
|
+
Time.local(2009, 3, 8, 4, 33),
|
246
|
+
Time.local(2009, 3, 8, 4, 34),
|
247
|
+
Time.local(2009, 3, 8, 4, 35),
|
248
|
+
]
|
249
|
+
schedule.first(expected.size).should == expected and
|
250
|
+
|
251
|
+
schedule = Crontab::Schedule.new('5 10 3-5,10 * *', Time.local(2009, 3, 8, 10, 30, 15))
|
252
|
+
expected = [
|
253
|
+
Time.local(2009, 3, 10, 10, 5),
|
254
|
+
Time.local(2009, 4, 3, 10, 5),
|
255
|
+
Time.local(2009, 4, 4, 10, 5),
|
256
|
+
Time.local(2009, 4, 5, 10, 5),
|
257
|
+
Time.local(2009, 4, 10, 10, 5),
|
258
|
+
Time.local(2009, 5, 3, 10, 5),
|
259
|
+
Time.local(2009, 5, 4, 10, 5),
|
260
|
+
Time.local(2009, 5, 5, 10, 5),
|
261
|
+
Time.local(2009, 5, 10, 10, 5),
|
262
|
+
Time.local(2009, 6, 3, 10, 5),
|
263
|
+
]
|
264
|
+
schedule.first(expected.size).should == expected
|
265
|
+
end
|
266
|
+
|
267
|
+
it 'should return timings matching day of month or day of week' do
|
268
|
+
schedule = Crontab::Schedule.new('0 0 1,10,11 3 0,1', Time.local(2009, 3, 8, 10, 30, 15))
|
269
|
+
expected = [
|
270
|
+
Time.local(2009, 3, 9, 0, 0),
|
271
|
+
Time.local(2009, 3, 10, 0, 0),
|
272
|
+
Time.local(2009, 3, 11, 0, 0),
|
273
|
+
Time.local(2009, 3, 15, 0, 0),
|
274
|
+
Time.local(2009, 3, 16, 0, 0),
|
275
|
+
Time.local(2009, 3, 22, 0, 0),
|
276
|
+
Time.local(2009, 3, 23, 0, 0),
|
277
|
+
Time.local(2009, 3, 29, 0, 0),
|
278
|
+
Time.local(2009, 3, 30, 0, 0),
|
279
|
+
]
|
280
|
+
schedule.first(expected.size).should == expected and
|
281
|
+
|
282
|
+
schedule = Crontab::Schedule.new('0 0 1,10,11 3 *', Time.local(2009, 3, 8, 10, 30, 15))
|
283
|
+
expected = [
|
284
|
+
Time.local(2009, 3, 10, 0, 0),
|
285
|
+
Time.local(2009, 3, 11, 0, 0),
|
286
|
+
]
|
287
|
+
schedule.first(expected.size).should == expected and
|
288
|
+
|
289
|
+
schedule = Crontab::Schedule.new('0 0 * 3 0,1', Time.local(2009, 3, 8, 10, 30, 15))
|
290
|
+
expected = [
|
291
|
+
Time.local(2009, 3, 9, 0, 0),
|
292
|
+
Time.local(2009, 3, 15, 0, 0),
|
293
|
+
Time.local(2009, 3, 16, 0, 0),
|
294
|
+
Time.local(2009, 3, 22, 0, 0),
|
295
|
+
Time.local(2009, 3, 23, 0, 0),
|
296
|
+
Time.local(2009, 3, 29, 0, 0),
|
297
|
+
Time.local(2009, 3, 30, 0, 0),
|
298
|
+
]
|
299
|
+
schedule.first(expected.size).should == expected
|
300
|
+
end
|
301
|
+
|
302
|
+
it 'should return timings until given time' do
|
303
|
+
schedule = Crontab::Schedule.new('* * * * *', Time.local(2009, 3, 8, 4, 30, 15))
|
304
|
+
expected = [
|
305
|
+
Time.local(2009, 3, 8, 4, 31),
|
306
|
+
Time.local(2009, 3, 8, 4, 32),
|
307
|
+
Time.local(2009, 3, 8, 4, 33),
|
308
|
+
Time.local(2009, 3, 8, 4, 34),
|
309
|
+
Time.local(2009, 3, 8, 4, 35),
|
310
|
+
Time.local(2009, 3, 8, 4, 36),
|
311
|
+
Time.local(2009, 3, 8, 4, 37),
|
312
|
+
Time.local(2009, 3, 8, 4, 38),
|
313
|
+
Time.local(2009, 3, 8, 4, 39),
|
314
|
+
Time.local(2009, 3, 8, 4, 40),
|
315
|
+
]
|
316
|
+
schedule.until(Time.local(2009, 3, 8, 4, 40, 10)).should == expected and
|
317
|
+
|
318
|
+
result = []
|
319
|
+
schedule.until(Time.local(2009, 3, 8, 4, 40, 10)) do |t|
|
320
|
+
result << t
|
321
|
+
end
|
322
|
+
result.should == expected and
|
323
|
+
|
324
|
+
schedule = Crontab::Schedule.new('*/20 0 21 * *', Time.local(2009, 3, 8, 4, 30, 15))
|
325
|
+
expected = [
|
326
|
+
Time.local(2009, 3, 21, 0, 0),
|
327
|
+
Time.local(2009, 3, 21, 0, 20),
|
328
|
+
Time.local(2009, 3, 21, 0, 40),
|
329
|
+
Time.local(2009, 4, 21, 0, 0),
|
330
|
+
Time.local(2009, 4, 21, 0, 20),
|
331
|
+
Time.local(2009, 4, 21, 0, 40),
|
332
|
+
Time.local(2009, 5, 21, 0, 00),
|
333
|
+
Time.local(2009, 5, 21, 0, 20),
|
334
|
+
Time.local(2009, 5, 21, 0, 40),
|
335
|
+
]
|
336
|
+
schedule.until(Time.local(2009, 6, 1)).should == expected
|
337
|
+
end
|
338
|
+
|
339
|
+
it 'should exclude invalid day of month' do
|
340
|
+
schedule = Crontab::Schedule.new('5 0 27,29,31 * *', Time.local(2009, 1, 1, 10, 30, 15))
|
341
|
+
expected = [
|
342
|
+
Time.local(2009, 1, 27, 0, 5),
|
343
|
+
Time.local(2009, 1, 29, 0, 5),
|
344
|
+
Time.local(2009, 1, 31, 0, 5),
|
345
|
+
Time.local(2009, 2, 27, 0, 5),
|
346
|
+
# Time.local(2009, 2, 29, 0, 5), # should exclude this!
|
347
|
+
# Time.local(2009, 2, 31, 0, 5), # should exclude this!
|
348
|
+
Time.local(2009, 3, 27, 0, 5),
|
349
|
+
Time.local(2009, 3, 29, 0, 5),
|
350
|
+
Time.local(2009, 3, 31, 0, 5),
|
351
|
+
]
|
352
|
+
|
353
|
+
schedule.first(expected.size).should == expected
|
354
|
+
end
|
355
|
+
end
|
356
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'spec_helper')
|
2
|
+
require 'crontab'
|
3
|
+
|
4
|
+
describe Crontab do
|
5
|
+
describe 'parsing' do
|
6
|
+
it 'should recognize entries' do
|
7
|
+
crontab = Crontab.parse(File.read('spec/data/crontab.txt'))
|
8
|
+
crontab.entries.size.should == 5 and
|
9
|
+
crontab.entries.should be_frozen
|
10
|
+
crontab.entries[0].tap do |e|
|
11
|
+
e.schedule.minutes.should == [8] and
|
12
|
+
e.schedule.hours.should == [3] and
|
13
|
+
e.schedule.day_of_months.should == (1..31).to_a and
|
14
|
+
e.schedule.months.should == (1..12).to_a and
|
15
|
+
e.schedule.day_of_weeks.should == [6]
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'should recognize variables assignments' do
|
20
|
+
crontab = Crontab.parse(File.read('spec/data/crontab.txt'))
|
21
|
+
crontab.env.should == {
|
22
|
+
'SHELL' => '/bin/zsh',
|
23
|
+
'MAIL_TO' => 'nobody@example.com',
|
24
|
+
'FOO' => 'foo',
|
25
|
+
'BAR' => 'bar',
|
26
|
+
'BAZ' => 'baz'
|
27
|
+
}
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
# 8 3 * * 6 run-parts $HOME/.cron.d/weekly
|
32
|
+
# 50 2 * * * run-parts $HOME/.cron.d/daily
|
33
|
+
# 3 * * * * run-parts $HOME/.cron.d/hourly
|
34
|
+
#
|
35
|
+
# */5 * * * * curl -sI http://example.com >/dev/null
|
36
|
+
#
|
37
|
+
# * * * * * awk 'BEGIN{ srand(); exit(rand() * 24 * 60.0 < 12.0 ? 0 : 1); }' && fortune
|
metadata
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: crontab
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 27
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 2
|
10
|
+
version: 0.0.2
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- OZAWA Sakuro
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2011-02-04 00:00:00 +09:00
|
19
|
+
default_executable:
|
20
|
+
dependencies: []
|
21
|
+
|
22
|
+
description:
|
23
|
+
email: github@2238club.org
|
24
|
+
executables: []
|
25
|
+
|
26
|
+
extensions: []
|
27
|
+
|
28
|
+
extra_rdoc_files: []
|
29
|
+
|
30
|
+
files:
|
31
|
+
- lib/crontab.rb
|
32
|
+
- lib/crontab/entry.rb
|
33
|
+
- lib/crontab/schedule.rb
|
34
|
+
- spec/crontab_spec.rb
|
35
|
+
- spec/crontab/entry_spec.rb
|
36
|
+
- spec/crontab/schedule_spec.rb
|
37
|
+
- crontab.gemspec
|
38
|
+
- Rakefile
|
39
|
+
- README.rdoc
|
40
|
+
has_rdoc: true
|
41
|
+
homepage: http://github.com/sakuro/crontab
|
42
|
+
licenses: []
|
43
|
+
|
44
|
+
post_install_message:
|
45
|
+
rdoc_options: []
|
46
|
+
|
47
|
+
require_paths:
|
48
|
+
- lib
|
49
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
hash: 3
|
55
|
+
segments:
|
56
|
+
- 0
|
57
|
+
version: "0"
|
58
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
59
|
+
none: false
|
60
|
+
requirements:
|
61
|
+
- - ">="
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
hash: 3
|
64
|
+
segments:
|
65
|
+
- 0
|
66
|
+
version: "0"
|
67
|
+
requirements: []
|
68
|
+
|
69
|
+
rubyforge_project: n/a
|
70
|
+
rubygems_version: 1.3.7
|
71
|
+
signing_key:
|
72
|
+
specification_version: 3
|
73
|
+
summary: A crontab(5) entry parser
|
74
|
+
test_files: []
|
75
|
+
|