Moderizing dev environment.

- Use rake-deveiate.
 - Fixes for README
 - Add History, break out Manifest
 - Remove keyword expansion constant REVISION
 - Use current Configurability APIs instead of the (now ancient) methods
 - Whitespace changes

FossilOrigin-Name: a778fb275af99a238d9d2311cc601c58adf80e28bd68a37e06acdfb6efeb018d
This commit is contained in:
Mahlon E. Smith 2023-03-21 18:21:32 +00:00
parent 07c927e13a
commit 2682000224
18 changed files with 210 additions and 227 deletions

View file

@ -0,0 +1,9 @@
Session.vim
certs/*
coverage/*
docs/*
gem.deps.rb.lock
*.so
tmp/*
lib/symphony/metronome/intervalexpression.rb

8
.gems
View file

@ -1,8 +0,0 @@
autotest -v4.4.6
configurability -v2.1.1
sequel -v5.4.0
sqlite3 -v1.3.9
symphony -v0.9.2
rspec -v3.3.1
timecop -v0.7.1
simplecov -v0.9.1

View file

@ -1,11 +0,0 @@
\.orig$
\.rej$
etc/.*\.(conf|yml)$
\.DS_Store
~$
pkg/
^ChangeLog$
^docs/
^coverage/
lib/symphony/metronome/intervalexpression.rb

View file

@ -1 +0,0 @@
bc6d5e12d577e1a982dfd4cd8ae63e90ce94f876 v0.2.1

1
.ruby-gemset Normal file
View file

@ -0,0 +1 @@
metronome

1
.ruby-version Normal file
View file

@ -0,0 +1 @@
3.2

32
.rvmrc
View file

@ -1,32 +0,0 @@
#!/usr/bin/env bash
# This is an RVM Project .rvmrc file, used to automatically load the ruby
# development environment upon cd'ing into the directory
environment_id="2.1.1@metronome"
if [[ -d "${rvm_path:-$HOME/.rvm}/environments" \
&& -s "${rvm_path:-$HOME/.rvm}/environments/$environment_id" ]]; then
echo "Using ${environment_id}"
. "${rvm_path:-$HOME/.rvm}/environments/$environment_id"
if [[ -s "${rvm_path:-$HOME/.rvm}/hooks/after_use" ]]; then
. "${rvm_path:-$HOME/.rvm}/hooks/after_use"
fi
else
# If the environment file has not yet been created, use the RVM CLI to select.
if ! rvm --create use "$environment_id"
then
echo "Failed to create RVM environment '${environment_id}'."
exit 1
fi
fi
filename=".gems"
if [[ -s "$filename" ]]; then
rvm gemset import "$filename"
fi

27
History.md Normal file
View file

@ -0,0 +1,27 @@
# Release History for Symphony-Metronome
---
## v0.3.0 [2023-03-20] Mahlon E. Smith <mahlon@martini.nu>
- Updates for Ruby 3.
- Dependency updates.
- Conversion to more modern ruby development tooling.
## v0.2.1 [2015-07-08] Mahlon E. Smith <mahlon@martini.nu>
Enhancements:
- Add "last run" timestamps for recurring events.
Fixes:
- Repair exact start times for recurring events, ie
'starting at 2015-01-01 09:00:00 run every other minute for 2 days'
## v0.1.0 [2014-04-22] Mahlon E. Smith <mahlon@martini.nu>
Initial release.

10
Manifest.txt Normal file
View file

@ -0,0 +1,10 @@
data/symphony-metronome/migrations/20140419_initial.rb
data/symphony-metronome/migrations/20141028_lastrun.rb
lib/symphony/metronome/intervalexpression.rb
lib/symphony/metronome/mixins.rb
lib/symphony/metronome/scheduledevent.rb
lib/symphony/metronome/scheduler.rb
lib/symphony/metronome.rb
lib/symphony/tasks/scheduletask.rb
History.md
README.md

View file

@ -1,15 +1,26 @@
# Metronome # Metronome
home
: https://code.martini.nu/fossil/symphony-metronome
docs
: https://martini.nu/docs/symphony-metronome
github_mirror
: https://github.com/mahlonsmith/symphony-metronome
## Description ## Description
Metronome is an interval scheduler and task runner. It can be used Metronome is an interval scheduler and task runner. It can be used locally as a
locally as a cron replacement, or as a network-wide job executor. cron replacement, or as a network-wide job executor. It is intended to be run
alongside Symphony, a Ruby AMQP event consumer.
Events are stored via simple database rows, and optionally managed Events are stored via simple database rows, and optionally themselves managed
via AMQP events. Interval/time values are expressed with intuitive via AMQP events. Interval/time values are expressed with intuitive English
English phrases, ie.: 'at 2pm', or 'Starting in 20 minutes, run every 10 phrases, ie.: 'at 2pm', or 'Starting in 20 minutes, run every 10 seconds and
seconds and then finish in 2 days', or 'execute 12 times during the next then finish in 2 days', or 'execute 12 times during the next minute'.
minute'.
## Synopsis ## Synopsis
@ -31,8 +42,8 @@ end
``` ```
And here's a simplistic AMQP message broadcaster, using existing And here's a simplistic timed AMQP message broadcaster, using existing Symphony
Symphony connection information: connection information:
``` ```
#!ruby #!ruby
@ -156,19 +167,58 @@ gem install symphony-metronome
## Contributing ## Contributing
You can check out the current development source with Mercurial You can check out the source via Fossil from the following uri:
[here](http://code.martini.nu/symphony-metronome), or via a mirror:
* github: https://github.com/mahlonsmith/Symphony-Metronome % fossil clone https://code.martini.nu/fossil/symphony-metronome
* SourceHut: https://hg.sr.ht/~mahlon/Symphony-Metronome
or via its GitHub mirror at:
% git clone https://github.com/mahlonsmith/Symphony-Metronome
After checking out the source, run: After checking out the source, run:
``` $ gem install -Ng
$ rake $ rake setup
```
This task will run the tests/specs and generate API documentation. This will install dependencies, and do any other necessary setup for
development.
Please report any issues
[here](https://code.martini.nu/fossil/symphony-metronome/tktnew).
## Authors
- Mahlon E. Smith <mahlon@martini.nu>
## License
Copyright (c) 2014-2023 Mahlon E. Smith
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of the author/s, nor the names of the project's
contributors may be used to endorse or promote products derived from this
software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
If you use [rvm](http://rvm.io/), entering the project directory will
install any required development dependencies.

152
Rakefile
View file

@ -1,28 +1,35 @@
#!/usr/bin/env rake # vim: set noet sta sw=4 ts=4 :
# vim: set nosta noet ts=4 sw=4: # -*- ruby -*-
require 'rake/clean' require 'rake/deveiate'
require 'pathname' require 'pathname'
PROJECT = 'metronome' Rake::DevEiate.setup( 'symphony-metronome' ) do |project|
BASEDIR = Pathname( __FILE__ ).dirname.relative_path_from( Pathname.pwd ) project.publish_to = 'martini.nu:martini/www/docs/symphony-metronome'
LIBDIR = BASEDIR + 'lib' + 'symphony' project.summary = <<~END_SUM
A natural language scheduling and task runner for Symphony.
END_SUM
project.description = <<~END_DESC
Metronome is a scheduler and task runner. It can be used locally as a
cron replacement, or as a network-wide job executor. Events are stored
via simple database rows, and optionally managed via AMQP events.
Interval/time values are expressed with reasonably intuitive English
phrases, ie.: 'at 2pm', or 'Starting in 20 minutes, run every 10 seconds
and then finish in 2 days'.
END_DESC
project.authors = [ 'Mahlon E. Smith <mahlon@martini.nu>' ]
project.rdoc_generator = :sixfish
end
CLOBBER.include( 'coverage' ) CLOBBER.include( 'coverage' )
$LOAD_PATH.unshift( LIBDIR.to_s ) BASEDIR = Pathname( __FILE__ ).dirname.relative_path_from( Pathname.pwd )
LIBDIR = BASEDIR + 'lib' + 'symphony'
EXPRESSION_RL = LIBDIR + 'metronome' + 'intervalexpression.rl' EXPRESSION_RL = LIBDIR + 'metronome' + 'intervalexpression.rl'
EXPRESSION_RB = LIBDIR + 'metronome' + 'intervalexpression.rb' EXPRESSION_RB = LIBDIR + 'metronome' + 'intervalexpression.rb'
CLOBBER.include( EXPRESSION_RB.to_s )
if Rake.application.options.trace
$trace = true
$stderr.puts '$trace is enabled'
end
# get the current library version
$version = ( LIBDIR + "#{PROJECT}.rb" ).read.split(/\n/).
select{|line| line =~ /VERSION =/}.first.match(/([\d|.]+)/)[1]
task :default => [ :spec, :docs, :package ] task :default => [ :spec, :docs, :package ]
@ -32,116 +39,7 @@ file EXPRESSION_RB
task EXPRESSION_RB => EXPRESSION_RL do |task| task EXPRESSION_RB => EXPRESSION_RL do |task|
sh 'ragel', '-R', '-T1', '-Ls', task.prerequisites.first sh 'ragel', '-R', '-T1', '-Ls', task.prerequisites.first
end end
task :spec => EXPRESSION_RB task :spec => EXPRESSION_RB
task :package => EXPRESSION_RB
########################################################################
### P A C K A G I N G
########################################################################
require 'rubygems'
require 'rubygems/package_task'
spec = Gem::Specification.new do |s|
s.email = 'mahlon@martini.nu'
s.homepage = 'http://projects.martini.nu/ruby-modules'
s.authors = [ 'Mahlon E. Smith <mahlon@martini.nu>' ]
s.platform = Gem::Platform::RUBY
s.summary = "A natural language scheduling and task runner."
s.name = 'symphony-' + PROJECT
s.version = $version
s.license = 'BSD'
s.has_rdoc = true
s.require_path = 'lib'
s.bindir = 'bin'
s.files = File.read( __FILE__ ).split( /^__END__/, 2 ).last.split
s.executables = %w[ metronome-exp ]
s.description = <<-EOF
Metronome is a scheduler and task runner. It can be used locally as a
cron replacement, or as a network-wide job executor. Events are stored
via simple database rows, and optionally managed via AMQP events.
Interval/time values are expressed with reasonably intuitive English
phrases, ie.: 'at 2pm', or 'Starting in 20 minutes, run every 10 seconds
and then finish in 2 days'.
EOF
s.required_rubygems_version = '>= 2.0.3'
s.required_ruby_version = '>= 2.0.0'
s.add_dependency 'symphony', '~> 0.11'
s.add_dependency 'sequel', '~> 5'
s.add_dependency 'sqlite3', '~> 1.3'
s.add_development_dependency 'rspec', '~> 3.3'
s.add_development_dependency 'simplecov', '~> 0.9'
s.add_development_dependency 'timecop', '~> 0.7'
end
Gem::PackageTask.new( spec ) do |pkg|
pkg.need_zip = true
pkg.need_tar = true
end
########################################################################
### D O C U M E N T A T I O N
########################################################################
begin
require 'rdoc/task'
desc 'Generate rdoc documentation'
RDoc::Task.new do |rdoc|
rdoc.name = :docs
rdoc.rdoc_dir = 'docs'
rdoc.main = "README.rdoc"
rdoc.rdoc_files = [ 'lib', *FileList['*.rdoc'] ]
end
RDoc::Task.new do |rdoc|
rdoc.name = :doc_coverage
rdoc.options = [ '-C1' ]
end
rescue LoadError
$stderr.puts "Omitting 'docs' tasks, rdoc doesn't seem to be installed."
end
########################################################################
### T E S T I N G
########################################################################
begin
require 'rspec/core/rake_task'
task :test => :spec
desc "Run specs"
RSpec::Core::RakeTask.new do |t|
t.pattern = "spec/**/*_spec.rb"
end
desc "Build a coverage report"
task :coverage do
ENV[ 'COVERAGE' ] = "yep"
Rake::Task[ :spec ].invoke
end
rescue LoadError
$stderr.puts "Omitting testing tasks, rspec doesn't seem to be installed."
end
########################################################################
### M A N I F E S T
########################################################################
__END__
bin/metronome-exp
data/symphony-metronome/migrations/20140419_initial.rb
data/symphony-metronome/migrations/20141028_lastrun.rb
lib/symphony/metronome/intervalexpression.rb
lib/symphony/metronome/intervalexpression.rl
lib/symphony/metronome/mixins.rb
lib/symphony/metronome/scheduledevent.rb
lib/symphony/metronome/scheduler.rb
lib/symphony/metronome.rb
lib/symphony/tasks/scheduletask.rb
README.rdoc

25
certs/mahlon.pem Normal file
View file

@ -0,0 +1,25 @@
-----BEGIN CERTIFICATE-----
MIIENDCCApygAwIBAgIBATANBgkqhkiG9w0BAQsFADAiMSAwHgYDVQQDDBdtYWhs
b24vREM9bWFydGluaS9EQz1udTAeFw0yMTAxMTcwMDQzMDZaFw0zMTAxMTUwMDQz
MDZaMCIxIDAeBgNVBAMMF21haGxvbi9EQz1tYXJ0aW5pL0RDPW51MIIBojANBgkq
hkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAsRZyNGaL3I8T2AkQsHKyixW10CY6T715
uOztbmZImekhmgE9Uj5xZCnUP4xG5ToJffgkxcbepyJwIHCjEQg7viL9EsA+rMNb
UX8dsa9jpvVD6nHAdoW8G0ee7SRBXhCfyNma8FtkDJfw2bwdKhxUKiHsULCSQ0Pd
p+4d5NnldgfB8cf4Hz9Ai/8FHacWnZVEiHa4Ngb5Fe42OUs+4XDQdpcgA7wCY633
q9rRVGK7MW9BzMv+hhQfElQMn1eDMgQVpO543viDT8JatwhhcYmKdzwTIIPAIybf
8MfJaimsh20OAqs3FAXNKjDVFbcXFfKUXXgVgMjUoEK5+Lp+pKPZXU4bIi5oYZqB
OttGPMD5rOWlAooWNQ7xbdHByUVqJmALSWHqPHdvVmAVsW8tNoB1qGbM+C6o80Ie
9H0389ja3TW4JK/0w/gFUmrVvYKRll44HaxS9nXNpiYBipbJmlR/R9qoe54ImQje
Z4vsWrWiDrK/oVYlUXOy7SE/jUAQF9UzAgMBAAGjdTBzMAkGA1UdEwQCMAAwCwYD
VR0PBAQDAgSwMB0GA1UdDgQWBBSx8TRqCmTPOARICKiZ3c66sIG3pTAcBgNVHREE
FTATgRFtYWhsb25AbWFydGluaS5udTAcBgNVHRIEFTATgRFtYWhsb25AbWFydGlu
aS5udTANBgkqhkiG9w0BAQsFAAOCAYEAHbiAZTe46/kp1Tkm4s6D30VBaYaAdaYG
bZIaAaHtJO9MbUNS0FA01fxQpptjpOQT3cNNf8CX8UHPTaSuPFMfgVWj1xiX7Byb
hqhUcUTENOuUxxWGDCa4orCFctc3yihojTKGtbhODHVSHf9DRDyalRcvmyWzxMFT
XtBS05OUXc9O1bKqzNaRc9nMGw6Y+V79hIex4mZlMBkhTeVKxeeweCXfELXOQRmB
FgPgUyQn0AaSpplx0YoWdy/99fEkXSMvgeEoiR1ApR6aUuTlvIr1yUgzVBpWU4mE
XC+Ig+3jhqufGyE/Do+1M7n5QLpgGfpy3QmoOiKeYt3XzR5Z7XoxCAaKHNRxVEga
ojmVnDNlLQkkZZkbFNGPHjCIBs7h+6eoIYvy/eQ82c4vd6w9rR4v9bKUL8NNkcSz
49pOzX5KHZLTS9DKeaP/xcGPz6C8MiwQdYrZarr2SHRASX1zFa79rkItO8kE6RDr
b6WDF79UvZ55ajtE00TiwqjQL/ZPEtbd
-----END CERTIFICATE-----

17
gem.deps.rb Normal file
View file

@ -0,0 +1,17 @@
source 'https://rubygems.org/'
gem 'sequel', '~> 5.66'
gem 'sorted_set', '~> 1.0'
gem 'sqlite3', '~> 1.6'
gem 'symphony', '~> 0.14'
gem 'yajl-ruby', '~> 1.4'
group :development do
gem 'rake-deveiate', '~> 0.22'
gem 'rspec', '~> 3.12'
gem 'rdoc-generator-sixfish', '~> 0.3'
gem 'simplecov', '~> 0.22'
gem 'timecop', '~> 0.9'
gem 'pry', '~> 0.14'
end

View file

@ -9,10 +9,7 @@ module Symphony::Metronome
Configurability Configurability
# Library version constant # Library version constant
VERSION = '0.2.3' VERSION = '0.3.0'
# Version-control revision constant
REVISION = %q$Revision: e3d11b2c9e48 $
# The name of the environment variable to check for config file overrides # The name of the environment variable to check for config file overrides
CONFIG_ENV = 'METRONOME_CONFIG' CONFIG_ENV = 'METRONOME_CONFIG'

View file

@ -239,7 +239,7 @@ class Symphony::Metronome::IntervalExpression
### an expression was generated, you can 'reconstitute' an interval ### an expression was generated, you can 'reconstitute' an interval
### object this way. ### object this way.
### ###
def self::parse( exp, time=nil ) def self::parse( exp, time=Time.now )
# Normalize the expression before parsing # Normalize the expression before parsing
# #
@ -250,7 +250,7 @@ class Symphony::Metronome::IntervalExpression
gsub( /([:\-])+/, '\1' ). # collapse multiple - or : chars gsub( /([:\-])+/, '\1' ). # collapse multiple - or : chars
gsub( /\.+$/, '' ) # trailing periods gsub( /\.+$/, '' ) # trailing periods
event = new( exp, time || Time.now ) event = new( exp, time )
data = event.instance_variable_get( :@data ) data = event.instance_variable_get( :@data )
# Ragel interface variables # Ragel interface variables

View file

@ -21,19 +21,17 @@ class Symphony::Metronome::ScheduledEvent
config_key :metronome config_key :metronome
# Configure defaults. ### Configurability API.
# ###
CONFIG_DEFAULTS = { configurability do
:db => 'sqlite:///tmp/metronome.db',
:splay => 0
}
class << self ##
# A Sequel-style DB connection URI. # A Sequel-style DB connection URI.
attr_reader :db setting :db, default: 'sqlite:///tmp/metronome.db'
##
# Adjust recurring intervals by a random window. # Adjust recurring intervals by a random window.
attr_reader :splay setting :splay, default: 0
end end
@ -62,7 +60,6 @@ class Symphony::Metronome::ScheduledEvent
### Delete any rows that are invalid expressions. ### Delete any rows that are invalid expressions.
### ###
def self::load def self::load
now = Time.now
events = SortedSet.new events = SortedSet.new
# Force reset the DB handle. # Force reset the DB handle.

View file

@ -17,22 +17,15 @@ class Symphony::Metronome::Scheduler
# Signals the daemon responds to. # Signals the daemon responds to.
SIGNALS = [ :HUP, :INT, :TERM ] SIGNALS = [ :HUP, :INT, :TERM ]
CONFIG_DEFAULTS = {
:listen => false
}
class << self ### Configurability API.
###
configurability do
# Should Metronome register and schedule events via AMQP? # Should Metronome register and schedule events via AMQP?
# If +false+, you'll need a separate way to add event actions # If +false+, you'll need a separate way to add event actions
# to the database, and manually HUP the daemon. # to the database, and manually HUP the daemon.
attr_reader :listen setting :listen, default: false
end
### Configurability API
###
def self::configure( config=nil )
config = self.defaults.merge( config || {} )
@listen = config.delete( :listen )
end end

View file

@ -33,11 +33,13 @@ describe Symphony::Metronome::ScheduledEvent do
Timecop.travel( time ) Timecop.travel( time )
end end
it 'applies migrations upon initial config' do it 'applies migrations upon initial config' do
migrations = described_class.db[ :schema_migrations ].all migrations = described_class.db[ :schema_migrations ].all
expect( migrations.first[:filename] ).to eq( '20140419_initial.rb' ) expect( migrations.first[:filename] ).to eq( '20140419_initial.rb' )
end end
it 'can load all stored events sorted by next execution time' do it 'can load all stored events sorted by next execution time' do
ds.insert( ds.insert(
:created => Time.now, :created => Time.now,
@ -61,6 +63,7 @@ describe Symphony::Metronome::ScheduledEvent do
expect( events.last.event.instance_variable_get(:@exp) ).to eq( 'at 3pm' ) expect( events.last.event.instance_variable_get(:@exp) ).to eq( 'at 3pm' )
end end
it 'removes invalid expressions from storage when loading' do it 'removes invalid expressions from storage when loading' do
ds.insert( ds.insert(
:created => Time.now, :created => Time.now,
@ -77,6 +80,7 @@ describe Symphony::Metronome::ScheduledEvent do
end end
end end
context 'an instance' do context 'an instance' do
let( :time ) { Time.at(1262376000) } let( :time ) { Time.at(1262376000) }
@ -94,6 +98,7 @@ describe Symphony::Metronome::ScheduledEvent do
expect( ev.runtime ).to eq( time ) expect( ev.runtime ).to eq( time )
end end
it 'can reschedule itself into the future when recurring (past start)' do it 'can reschedule itself into the future when recurring (past start)' do
ev = described_class.new( ev = described_class.new(
:created => time, :created => time,
@ -107,6 +112,7 @@ describe Symphony::Metronome::ScheduledEvent do
expect( ev.runtime ).to be >= time + 3600 + 30 expect( ev.runtime ).to be >= time + 3600 + 30
end end
it 'can reschedule itself into the future when recurring (recently run)' do it 'can reschedule itself into the future when recurring (recently run)' do
ds.insert( ds.insert(
:created => time, :created => time,
@ -120,9 +126,10 @@ describe Symphony::Metronome::ScheduledEvent do
ev.reset_runtime ev.reset_runtime
end end
expect( ev.runtime ).to be >= time + 18 expect( ev.runtime ).to be >= time + 17
end end
it 'removes itself when firing if expired' do it 'removes itself when firing if expired' do
ds.insert( ds.insert(
:created => time, :created => time,
@ -135,6 +142,7 @@ describe Symphony::Metronome::ScheduledEvent do
expect( ds.count ).to eq( 0 ) expect( ds.count ).to eq( 0 )
end end
it 'yields a deserialized options hash if okay to fire' do it 'yields a deserialized options hash if okay to fire' do
ev = described_class.new( ev = described_class.new(
:created => time, :created => time,
@ -150,6 +158,7 @@ describe Symphony::Metronome::ScheduledEvent do
expect( res ).to be( 12 ) expect( res ).to be( 12 )
end end
it "won't re-fire recurring events if they already fired within their interval window" do it "won't re-fire recurring events if they already fired within their interval window" do
ds.insert( ds.insert(
:created => time, :created => time,
@ -170,6 +179,7 @@ describe Symphony::Metronome::ScheduledEvent do
expect( res ).to be( 0 ) expect( res ).to be( 0 )
end end
it 'randomizes start times if a splay is configured' do it 'randomizes start times if a splay is configured' do
described_class.instance_variable_set( :@splay, 5 ) described_class.instance_variable_set( :@splay, 5 )