symphony 0.4.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -71,5 +71,151 @@ module Symphony
71
71
  end # module MethodUtilities
72
72
 
73
73
 
74
+ # Functions for time calculations
75
+ module TimeFunctions
76
+
77
+ ###############
78
+ module_function
79
+ ###############
80
+
81
+ ### Calculate the (approximate) number of seconds that are in +count+ of the
82
+ ### given +unit+ of time.
83
+ def calculate_seconds( count, unit )
84
+ return case unit
85
+ when :seconds, :second
86
+ count
87
+ when :minutes, :minute
88
+ count * 60
89
+ when :hours, :hour
90
+ count * 3600
91
+ when :days, :day
92
+ count * 86400
93
+ when :weeks, :week
94
+ count * 604800
95
+ when :fortnights, :fortnight
96
+ count * 1209600
97
+ when :months, :month
98
+ count * 2592000
99
+ when :years, :year
100
+ count * 31557600
101
+ else
102
+ raise ArgumentError, "don't know how to calculate seconds in a %p" % [ unit ]
103
+ end
104
+ end
105
+
106
+ end # module TimeFunctions
107
+
108
+
109
+ # Refinements to Numeric to add time-related convenience methods
110
+ module TimeRefinements
111
+ refine Numeric do
112
+
113
+ ### Number of seconds (returns receiver unmodified)
114
+ def seconds
115
+ return self
116
+ end
117
+ alias_method :second, :seconds
118
+
119
+ ### Returns number of seconds in <receiver> minutes
120
+ def minutes
121
+ return TimeFunctions.calculate_seconds( self, :minutes )
122
+ end
123
+ alias_method :minute, :minutes
124
+
125
+ ### Returns the number of seconds in <receiver> hours
126
+ def hours
127
+ return TimeFunctions.calculate_seconds( self, :hours )
128
+ end
129
+ alias_method :hour, :hours
130
+
131
+ ### Returns the number of seconds in <receiver> days
132
+ def days
133
+ return TimeFunctions.calculate_seconds( self, :day )
134
+ end
135
+ alias_method :day, :days
136
+
137
+ ### Return the number of seconds in <receiver> weeks
138
+ def weeks
139
+ return TimeFunctions.calculate_seconds( self, :weeks )
140
+ end
141
+ alias_method :week, :weeks
142
+
143
+ ### Returns the number of seconds in <receiver> fortnights
144
+ def fortnights
145
+ return TimeFunctions.calculate_seconds( self, :fortnights )
146
+ end
147
+ alias_method :fortnight, :fortnights
148
+
149
+ ### Returns the number of seconds in <receiver> months (approximate)
150
+ def months
151
+ return TimeFunctions.calculate_seconds( self, :months )
152
+ end
153
+ alias_method :month, :months
154
+
155
+ ### Returns the number of seconds in <receiver> years (approximate)
156
+ def years
157
+ return TimeFunctions.calculate_seconds( self, :years )
158
+ end
159
+ alias_method :year, :years
160
+
161
+
162
+ ### Returns the Time <receiver> number of seconds before the
163
+ ### specified +time+. E.g., 2.hours.before( header.expiration )
164
+ def before( time )
165
+ return time - self
166
+ end
167
+
168
+
169
+ ### Returns the Time <receiver> number of seconds ago. (e.g.,
170
+ ### expiration > 2.hours.ago )
171
+ def ago
172
+ return self.before( ::Time.now )
173
+ end
174
+
175
+
176
+ ### Returns the Time <receiver> number of seconds after the given +time+.
177
+ ### E.g., 10.minutes.after( header.expiration )
178
+ def after( time )
179
+ return time + self
180
+ end
181
+
182
+
183
+ ### Return a new Time <receiver> number of seconds from now.
184
+ def from_now
185
+ return self.after( ::Time.now )
186
+ end
187
+
188
+
189
+ ### Return a string describing approximately the amount of time in
190
+ ### <receiver> number of seconds.
191
+ def timeperiod
192
+ return case
193
+ when self < 1.minute
194
+ 'less than a minute'
195
+ when self < 50.minutes
196
+ '%d minutes' % [ (self.to_f / 1.minute).ceil ]
197
+ when self < 90.minutes
198
+ 'about an hour'
199
+ when self < 18.hours
200
+ "%d hours" % [ (self.to_f / 1.hour).ceil ]
201
+ when self < 30.hours
202
+ 'about a day'
203
+ when self < 1.week
204
+ "%d days" % [ (self.to_f / 1.day).ceil ]
205
+ when self < 2.weeks
206
+ 'about one week'
207
+ when self < 3.months
208
+ "%d weeks" % [ (self.to_f / 1.week).ceil ]
209
+ when self < 18.months
210
+ "%d months" % [ (self.to_f / 1.month).ceil ]
211
+ else
212
+ "%d years" % [ (self.to_f / 1.year).ceil ]
213
+ end
214
+ end
215
+
216
+ end # refine Numeric
217
+ end # module TimeRefinements
218
+
219
+ end # module Symphony
220
+
74
221
 
75
- end # module Symphony
data/lib/symphony/task.rb CHANGED
@@ -25,7 +25,7 @@ class Symphony::Task
25
25
 
26
26
 
27
27
  # Signal to reset to defaults for the child
28
- SIGNALS = %i[ INT TERM HUP CHLD WINCH ]
28
+ SIGNALS = %i[ INT TERM HUP ]
29
29
 
30
30
  # Valid work model types
31
31
  WORK_MODELS = %i[ longlived oneshot ]
@@ -319,10 +319,6 @@ class Symphony::Task
319
319
  self.on_interrupt
320
320
  when :HUP
321
321
  self.on_hangup
322
- when :CHLD
323
- self.on_child_exit
324
- when :WINCH
325
- self.on_window_size_change
326
322
  else
327
323
  self.log.warn "Unhandled signal %s" % [ sig ]
328
324
  end
@@ -379,19 +375,6 @@ class Symphony::Task
379
375
  end
380
376
 
381
377
 
382
- ### Handle a child process exiting.
383
- def on_child_exit
384
- self.log.info "Child exited."
385
- Process.waitpid( 0, Process::WNOHANG )
386
- end
387
-
388
-
389
- ### Handle a window size change event. No-op by default.
390
- def on_window_size_change
391
- self.log.info "Window size changed."
392
- end
393
-
394
-
395
378
  ### Handle a hangup signal by re-reading the config and restarting.
396
379
  def on_hangup
397
380
  self.log.info "Hangup signal."
@@ -0,0 +1,481 @@
1
+ # vim: set nosta noet ts=4 sw=4 ft=rspec:
2
+
3
+ require_relative '../helpers'
4
+ require 'symphony/intervalexpression'
5
+ require 'symphony/mixins'
6
+
7
+ using Symphony::TimeRefinements
8
+
9
+
10
+ #####################################################################
11
+ ### C O N T E X T S
12
+ #####################################################################
13
+
14
+ describe Symphony::IntervalExpression do
15
+
16
+ let( :past ) { Time.at(1262376000) }
17
+
18
+ it "can't be instantiated directly" do
19
+ expect { described_class.new }.to raise_error( NoMethodError )
20
+ end
21
+
22
+ it "raises an exception if unable to parse the expression" do
23
+ expect {
24
+ described_class.parse( 'wut!' )
25
+ }.to raise_error( Symphony::TimeParseError, /unable to parse/ )
26
+ end
27
+
28
+ it "normalizes the expression before attempting to parse it" do
29
+ allow( Time ).to receive( :now ).and_return( past )
30
+ parsed = described_class.parse( '\'";At 2014---01-01 14::00(' )
31
+ expect( parsed.to_s ).to eq( 'at 2014-01-01 14:00' )
32
+ end
33
+
34
+ it "can parse the expression, offset from a different time" do
35
+ parsed = described_class.parse( 'every 5 seconds ending in an hour', past )
36
+ expect( parsed.starting ).to eq( past )
37
+ expect( parsed.ending ).to eq( past + 3600 )
38
+ end
39
+
40
+ it "is comparable" do
41
+ p1 = described_class.parse( 'at 2pm', past )
42
+ p2 = described_class.parse( 'at 3pm', past )
43
+ p3 = described_class.parse( 'at 2:00pm', past )
44
+
45
+ expect( p1 ).to be < p2
46
+ expect( p2 ).to be > p1
47
+ expect( p1 ).to eq( p3 )
48
+ end
49
+
50
+ it "won't allow scheduling dates in the past" do
51
+ expect {
52
+ described_class.parse( 'on 1999-01-01' )
53
+ }.to raise_error( Symphony::TimeParseError, /schedule in the past/ )
54
+ end
55
+
56
+ it "doesn't allow intervals of 0" do
57
+ expect {
58
+ described_class.parse( 'every 0 seconds' )
59
+ }.to raise_error( Symphony::TimeParseError, /unable to parse/ )
60
+ end
61
+
62
+
63
+ context 'exact times and dates' do
64
+
65
+ # stub for Time, tests are from this 'stuck' point:
66
+ # 2010-01-01 12:00
67
+ #
68
+ before( :each ) do
69
+ allow( Time ).to receive( :now ).and_return( past )
70
+ end
71
+
72
+ it 'at 2pm' do |example|
73
+ parsed = described_class.parse( example.description )
74
+ expect( parsed.valid ).to be
75
+ expect( parsed.recurring ).to be_falsey
76
+ expect( parsed.interval ).to be( 7200.0 )
77
+ end
78
+
79
+ it 'at 2:30pm' do |example|
80
+ parsed = described_class.parse( example.description )
81
+ expect( parsed.valid ).to be_truthy
82
+ expect( parsed.interval ).to be( 9000.0 )
83
+ end
84
+
85
+ it "pushes ambiguous times in today's past into tomorrow (at 11am)" do
86
+ parsed = described_class.parse( 'at 11am' )
87
+ expect( parsed.valid ).to be_truthy
88
+ expect( parsed.interval ).to be( 82800.0 )
89
+ end
90
+
91
+ it 'on 2010-01-02' do |example|
92
+ parsed = described_class.parse( example.description )
93
+ expect( parsed.valid ).to be_truthy
94
+ expect( parsed.interval ).to be( 43200.0 )
95
+ end
96
+
97
+ it 'on 2010-01-02 12:00' do |example|
98
+ parsed = described_class.parse( example.description )
99
+ expect( parsed.valid ).to be_truthy
100
+ expect( parsed.interval ).to be( 86400.0 )
101
+ end
102
+
103
+ it 'on 2010-01-02 12:00:01' do |example|
104
+ parsed = described_class.parse( example.description )
105
+ expect( parsed.valid ).to be_truthy
106
+ expect( parsed.interval ).to be( 86401.0 )
107
+ end
108
+
109
+ it 'correctly timeboxes the expression' do
110
+ parsed = described_class.parse( 'at 2pm' )
111
+ expect( parsed.valid ).to be_truthy
112
+ expect( parsed.interval ).to be( 7200.0 )
113
+ expect( parsed.ending ).to be_nil
114
+ expect( parsed.recurring ).to be_falsey
115
+ expect( parsed.starting ).to eq( past )
116
+ end
117
+
118
+ it 'always sets a start time if one is not specified' do
119
+ parsed = described_class.parse( 'at 2pm' )
120
+ expect( parsed.valid ).to be_truthy
121
+ expect( parsed.recurring ).to be_falsey
122
+ expect( parsed.starting ).to eq( past )
123
+ expect( parsed.interval ).to be( 7200.0 )
124
+ end
125
+ end
126
+
127
+ context 'one-shot intervals' do
128
+
129
+ # stub for Time, tests are from this 'stuck' point:
130
+ # 2010-01-01 12:00
131
+ #
132
+ before( :each ) do
133
+ allow( Time ).to receive( :now ).and_return( past )
134
+ end
135
+
136
+ it 'in 30 seconds' do |example|
137
+ parsed = described_class.parse( example.description )
138
+ expect( parsed.valid ).to be_truthy
139
+ expect( parsed.recurring ).to be_falsey
140
+ expect( parsed.interval ).to be( 30.0 )
141
+ end
142
+
143
+ it 'in 30 seconds from now' do |example|
144
+ parsed = described_class.parse( example.description )
145
+ expect( parsed.valid ).to be_truthy
146
+ expect( parsed.interval ).to be( 30.0 )
147
+ end
148
+
149
+ it 'in an hour from now' do |example|
150
+ parsed = described_class.parse( example.description )
151
+ expect( parsed.valid ).to be_truthy
152
+ expect( parsed.interval ).to be( 3600.0 )
153
+ end
154
+
155
+ it 'in a minute' do |example|
156
+ parsed = described_class.parse( example.description )
157
+ expect( parsed.valid ).to be_truthy
158
+ expect( parsed.interval ).to be( 60.0 )
159
+ end
160
+
161
+ it 'correctly timeboxes the expression' do
162
+ parsed = described_class.parse( 'in 30 seconds' )
163
+ expect( parsed.valid ).to be_truthy
164
+ expect( parsed.interval ).to be( 30.0 )
165
+ expect( parsed.ending ).to be_nil
166
+ expect( parsed.recurring ).to be_falsey
167
+ expect( parsed.starting ).to eq( past )
168
+ end
169
+
170
+ it 'always sets a start time if one is not specified' do
171
+ parsed = described_class.parse( 'in 5 seconds' )
172
+ expect( parsed.valid ).to be_truthy
173
+ expect( parsed.recurring ).to be_falsey
174
+ expect( parsed.starting ).to eq( past )
175
+ expect( parsed.interval ).to be( 5.0 )
176
+ end
177
+
178
+ it 'ignores end specifications for non-recurring run times' do
179
+ parsed = described_class.parse( 'run at 2010-01-02 end at 2010-03-01' )
180
+ expect( parsed.valid ).to be_truthy
181
+ expect( parsed.recurring ).to be_falsey
182
+ expect( parsed.starting ).to eq( past )
183
+ expect( parsed.ending ).to be_falsey
184
+ expect( parsed.interval ).to be( 43200.0 )
185
+ end
186
+ end
187
+
188
+ context 'repeating intervals' do
189
+
190
+ # stub for Time, tests are from this 'stuck' point:
191
+ # 2010-01-01 12:00
192
+ #
193
+ before( :each ) do
194
+ allow( Time ).to receive( :now ).and_return( past )
195
+ end
196
+
197
+ it 'every 500 milliseconds' do |example|
198
+ parsed = described_class.parse( example.description )
199
+ expect( parsed.valid ).to be_truthy
200
+ expect( parsed.recurring ).to be_truthy
201
+ expect( parsed.interval ).to be( 0.5 )
202
+ end
203
+
204
+ it 'every 30 seconds' do |example|
205
+ parsed = described_class.parse( example.description )
206
+ expect( parsed.valid ).to be_truthy
207
+ expect( parsed.recurring ).to be_truthy
208
+ expect( parsed.interval ).to be( 30.0 )
209
+ end
210
+
211
+ it 'once an hour' do |example|
212
+ parsed = described_class.parse( example.description )
213
+ expect( parsed.valid ).to be_truthy
214
+ expect( parsed.recurring ).to be_truthy
215
+ expect( parsed.interval ).to be( 3600.0 )
216
+ end
217
+
218
+ it 'once a minute' do |example|
219
+ parsed = described_class.parse( example.description )
220
+ expect( parsed.valid ).to be_truthy
221
+ expect( parsed.recurring ).to be_truthy
222
+ expect( parsed.interval ).to be( 60.0 )
223
+ end
224
+
225
+ it 'once per week' do |example|
226
+ parsed = described_class.parse( example.description )
227
+ expect( parsed.valid ).to be_truthy
228
+ expect( parsed.recurring ).to be_truthy
229
+ expect( parsed.interval ).to be( 604800.0 )
230
+ end
231
+
232
+ it 'every day' do |example|
233
+ parsed = described_class.parse( example.description )
234
+ expect( parsed.valid ).to be_truthy
235
+ expect( parsed.recurring ).to be_truthy
236
+ expect( parsed.interval ).to be( 86400.0 )
237
+ end
238
+
239
+ it 'every other day' do |example|
240
+ parsed = described_class.parse( example.description )
241
+ expect( parsed.valid ).to be_truthy
242
+ expect( parsed.recurring ).to be_truthy
243
+ expect( parsed.interval ).to be( 172800.0 )
244
+ end
245
+
246
+ it 'always sets a start time if one is not specified' do
247
+ parsed = described_class.parse( 'every 5 seconds' )
248
+ expect( parsed.valid ).to be_truthy
249
+ expect( parsed.recurring ).to be_truthy
250
+ expect( parsed.starting ).to eq( past )
251
+ expect( parsed.interval ).to be( 5.0 )
252
+ end
253
+ end
254
+
255
+ context 'repeating intervals with only an expiration date' do
256
+
257
+ # stub for Time, tests are from this 'stuck' point:
258
+ # 2010-01-01 12:00
259
+ #
260
+ before( :each ) do
261
+ allow( Time ).to receive( :now ).and_return( past )
262
+ end
263
+
264
+ it 'every day ending in 1 week' do |example|
265
+ parsed = described_class.parse( example.description )
266
+ expect( parsed.valid ).to be_truthy
267
+ expect( parsed.recurring ).to be_truthy
268
+ expect( parsed.interval ).to be( 86400.0 )
269
+ expect( parsed.ending ).to eq( past + 604800 )
270
+ end
271
+
272
+ it 'once a day finishing in a week from now' do |example|
273
+ parsed = described_class.parse( example.description )
274
+ expect( parsed.valid ).to be_truthy
275
+ expect( parsed.recurring ).to be_truthy
276
+ expect( parsed.interval ).to be( 86400.0 )
277
+ expect( parsed.ending ).to eq( past + 604800 )
278
+ end
279
+
280
+ it 'once a day until 2010-02-01' do |example|
281
+ parsed = described_class.parse( example.description )
282
+ expect( parsed.valid ).to be_truthy
283
+ expect( parsed.recurring ).to be_truthy
284
+ expect( parsed.interval ).to be( 86400.0 )
285
+ expect( parsed.ending ).to eq( past + 2635200 )
286
+ end
287
+
288
+ it 'once a day end on 2010-02-01 00:00:10' do |example|
289
+ parsed = described_class.parse( example.description )
290
+ expect( parsed.valid ).to be_truthy
291
+ expect( parsed.recurring ).to be_truthy
292
+ expect( parsed.interval ).to be( 86400.0 )
293
+ expect( parsed.ending ).to eq( past + 2635210 )
294
+ end
295
+
296
+ it 'always sets a start time if one is not specified' do
297
+ parsed = described_class.parse( 'every 5 seconds ending in 1 week' )
298
+ expect( parsed.valid ).to be_truthy
299
+ expect( parsed.recurring ).to be_truthy
300
+ expect( parsed.starting ).to eq( past )
301
+ expect( parsed.interval ).to be( 5.0 )
302
+ expect( parsed.ending ).to eq( past + 604800 )
303
+ end
304
+ end
305
+
306
+ context 'repeating intervals with only a start time' do
307
+
308
+ # stub for Time, tests are from this 'stuck' point:
309
+ # 2010-01-01 12:00
310
+ #
311
+ before( :each ) do
312
+ allow( Time ).to receive( :now ).and_return( past )
313
+ end
314
+
315
+ it "won't allow explicit start times with non-recurring run times" do
316
+ expect {
317
+ described_class.parse( 'start at 2010-02-01 run at 2010-02-01' )
318
+ }.to raise_error( Symphony::TimeParseError, /use 'at \[datetime\]' instead/ )
319
+ end
320
+
321
+ it 'starting in 5 minutes, run once a second' do |example|
322
+ parsed = described_class.parse( example.description )
323
+ expect( parsed.valid ).to be_truthy
324
+ expect( parsed.recurring ).to be_truthy
325
+ expect( parsed.starting ).to eq( past + 300 )
326
+ expect( parsed.interval ).to be( 1.0 )
327
+ end
328
+
329
+ it 'starting in a day execute every 3 minutes' do |example|
330
+ parsed = described_class.parse( example.description )
331
+ expect( parsed.valid ).to be_truthy
332
+ expect( parsed.recurring ).to be_truthy
333
+ expect( parsed.starting ).to eq( past + 86400 )
334
+ expect( parsed.interval ).to be( 180.0 )
335
+ end
336
+
337
+ it 'start at 2010-01-02 execute every 1 minute' do |example|
338
+ parsed = described_class.parse( example.description )
339
+ expect( parsed.valid ).to be_truthy
340
+ expect( parsed.recurring ).to be_truthy
341
+ expect( parsed.starting ).to eq( past + 43200 )
342
+ expect( parsed.interval ).to be( 60.0 )
343
+ end
344
+
345
+ it 'always sets a start time if one is not specified' do
346
+ parsed = described_class.parse( 'every 5 seconds' )
347
+ expect( parsed.valid ).to be_truthy
348
+ expect( parsed.recurring ).to be_truthy
349
+ expect( parsed.starting ).to eq( past )
350
+ expect( parsed.interval ).to be( 5.0 )
351
+ end
352
+ end
353
+
354
+ context 'intervals with start and end times' do
355
+
356
+ # stub for Time, tests are from this 'stuck' point:
357
+ # 2010-01-01 12:00
358
+ #
359
+ before( :each ) do
360
+ allow( Time ).to receive( :now ).and_return( past )
361
+ end
362
+
363
+ it 'beginning in 1 hour from now run every 5 seconds ending on 2010-01-02' do |example|
364
+ parsed = described_class.parse( example.description )
365
+ expect( parsed.valid ).to be_truthy
366
+ expect( parsed.recurring ).to be_truthy
367
+ expect( parsed.starting ).to eq( past + 3600 )
368
+ expect( parsed.interval ).to be( 5.0 )
369
+ expect( parsed.ending ).to eq( past + 43200 )
370
+ end
371
+
372
+ it 'starting in 1 hour, run every 5 seconds and finish at 3pm' do |example|
373
+ parsed = described_class.parse( example.description )
374
+ expect( parsed.valid ).to be_truthy
375
+ expect( parsed.recurring ).to be_truthy
376
+ expect( parsed.starting ).to eq( past + 3600 )
377
+ expect( parsed.interval ).to be( 5.0 )
378
+ expect( parsed.ending ).to eq( past + 3600 * 3 )
379
+ end
380
+
381
+ it 'begin in an hour run every 5 seconds and then stop at 3pm' do |example|
382
+ parsed = described_class.parse( example.description )
383
+ expect( parsed.valid ).to be_truthy
384
+ expect( parsed.recurring ).to be_truthy
385
+ expect( parsed.starting ).to eq( past + 3600 )
386
+ expect( parsed.interval ).to be( 5.0 )
387
+ expect( parsed.ending ).to eq( past + 3600 * 3 )
388
+ end
389
+
390
+ it 'start at 2010-01-02 10:00 and then run each minute for the next 6 days' do |example|
391
+ parsed = described_class.parse( example.description )
392
+ expect( parsed.valid ).to be_truthy
393
+ expect( parsed.recurring ).to be_truthy
394
+ expect( parsed.starting ).to eq( past + 43200 + 36000 )
395
+ expect( parsed.interval ).to be( 60.0 )
396
+ expect( parsed.ending ).to eq( Time.parse('2010-01-02 10:00') + 86400 * 6 )
397
+ end
398
+ end
399
+
400
+ context 'intervals with a count' do
401
+
402
+ # stub for Time, tests are from this 'stuck' point:
403
+ # 2010-01-01 12:00
404
+ #
405
+ before( :each ) do
406
+ allow( Time ).to receive( :now ).and_return( past )
407
+ end
408
+
409
+ it "won't allow count multipliers without an interval nor an end date" do
410
+ expect {
411
+ described_class.parse( 'run 10 times' )
412
+ }.to raise_error( Symphony::TimeParseError, /end date or interval is required/ )
413
+ end
414
+
415
+ it '10 times a minute for 2 days' do |example|
416
+ parsed = described_class.parse( example.description )
417
+ expect( parsed.multiplier ).to be( 10 )
418
+ expect( parsed.recurring ).to be_truthy
419
+ expect( parsed.starting ).to eq( past )
420
+ expect( parsed.interval ).to be( 6.0 )
421
+ expect( parsed.ending ).to eq( past + 86400 * 2 )
422
+ end
423
+
424
+ it 'run 45 times every hour' do |example|
425
+ parsed = described_class.parse( example.description )
426
+ expect( parsed.multiplier ).to be( 45 )
427
+ expect( parsed.recurring ).to be_truthy
428
+ expect( parsed.starting ).to eq( past )
429
+ expect( parsed.interval ).to be( 80.0 )
430
+ expect( parsed.ending ).to be_nil
431
+ end
432
+
433
+ it 'start at 2010-01-02 run 12 times and end on 2010-01-03' do |example|
434
+ parsed = described_class.parse( example.description )
435
+ expect( parsed.multiplier ).to be( 12 )
436
+ expect( parsed.recurring ).to be_truthy
437
+ expect( parsed.starting ).to eq( past + 43200 )
438
+ expect( parsed.interval ).to be( 7200.0 )
439
+ expect( parsed.ending ).to eq( past + 86400 + 43200 )
440
+ end
441
+
442
+ it 'starting in an hour from now run 6 times a minute for 2 hours' do |example|
443
+ parsed = described_class.parse( example.description )
444
+ expect( parsed.multiplier ).to be( 6 )
445
+ expect( parsed.recurring ).to be_truthy
446
+ expect( parsed.starting ).to eq( past + 3600 )
447
+ expect( parsed.interval ).to be( 10.0 )
448
+ expect( parsed.ending ).to eq( past + 3600 * 3 )
449
+ end
450
+
451
+ it 'beginning a day from now, run 30 times per minute and finish in 2 weeks' do |example|
452
+ parsed = described_class.parse( example.description )
453
+ expect( parsed.multiplier ).to be( 30 )
454
+ expect( parsed.recurring ).to be_truthy
455
+ expect( parsed.starting ).to eq( past + 86400 )
456
+ expect( parsed.interval ).to be( 2.0 )
457
+ expect( parsed.ending ).to eq( past + 1209600 + 86400 )
458
+ end
459
+ end
460
+
461
+ context "when checking if it's okay to run" do
462
+
463
+ it 'returns true if the interval is within bounds' do
464
+ parsed = described_class.parse( 'at 2pm' )
465
+ expect( parsed.fire? ).to be_truthy
466
+ end
467
+
468
+ it 'returns nil if the ending (expiration) date has passed' do
469
+ allow( Time ).to receive( :now ).and_return( past )
470
+ parsed = described_class.parse( 'every minute' )
471
+ parsed.instance_variable_set( :@ending, past - 30 )
472
+ expect( parsed.fire? ).to be_nil
473
+ end
474
+
475
+ it 'returns false if the starting window has yet to occur' do
476
+ parsed = described_class.parse( 'starting in 2 hours run each minute' )
477
+ expect( parsed.fire? ).to be_falsey
478
+ end
479
+ end
480
+ end
481
+