sakuro-crontab 0.0.2
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.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 +61 -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,61 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: sakuro-crontab
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- OZAWA Sakuro
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-05-16 00:00:00 -07:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description:
|
17
|
+
email: github@2238club.org
|
18
|
+
executables: []
|
19
|
+
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files: []
|
23
|
+
|
24
|
+
files:
|
25
|
+
- lib/crontab.rb
|
26
|
+
- lib/crontab/entry.rb
|
27
|
+
- lib/crontab/schedule.rb
|
28
|
+
- spec/crontab_spec.rb
|
29
|
+
- spec/crontab/entry_spec.rb
|
30
|
+
- spec/crontab/schedule_spec.rb
|
31
|
+
- crontab.gemspec
|
32
|
+
- Rakefile
|
33
|
+
- README.rdoc
|
34
|
+
has_rdoc: true
|
35
|
+
homepage: http://github.com/sakuro/crontab
|
36
|
+
post_install_message:
|
37
|
+
rdoc_options: []
|
38
|
+
|
39
|
+
require_paths:
|
40
|
+
- lib
|
41
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
42
|
+
requirements:
|
43
|
+
- - ">="
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: "0"
|
46
|
+
version:
|
47
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
48
|
+
requirements:
|
49
|
+
- - ">="
|
50
|
+
- !ruby/object:Gem::Version
|
51
|
+
version: "0"
|
52
|
+
version:
|
53
|
+
requirements: []
|
54
|
+
|
55
|
+
rubyforge_project: n/a
|
56
|
+
rubygems_version: 1.2.0
|
57
|
+
signing_key:
|
58
|
+
specification_version: 2
|
59
|
+
summary: A crontab(5) entry parser
|
60
|
+
test_files: []
|
61
|
+
|