symphony 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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
+