diff --git a/lib/puppet/type/schedule.rb b/lib/puppet/type/schedule.rb index 4de508c81..89c8aaaad 100755 --- a/lib/puppet/type/schedule.rb +++ b/lib/puppet/type/schedule.rb @@ -1,421 +1,466 @@ module Puppet newtype(:schedule) do @doc = <<-EOT Define schedules for Puppet. Resources can be limited to a schedule by using the [`schedule`](http://docs.puppetlabs.com/references/latest/metaparameter.html#schedule) metaparameter. Currently, **schedules can only be used to stop a resource from being applied;** they cannot cause a resource to be applied when it otherwise wouldn't be, and they cannot accurately specify a time when a resource should run. Every time Puppet applies its configuration, it will apply the set of resources whose schedule does not eliminate them from running right then, but there is currently no system in place to guarantee that a given resource runs at a given time. If you specify a very restrictive schedule and Puppet happens to run at a time within that schedule, then the resources will get applied; otherwise, that work may never get done. Thus, it is advisable to use wider scheduling (e.g., over a couple of hours) combined with periods and repetitions. For instance, if you wanted to restrict certain resources to only running once, between the hours of two and 4 AM, then you would use this schedule: schedule { 'maint': range => "2 - 4", period => daily, repeat => 1, } With this schedule, the first time that Puppet runs between 2 and 4 AM, all resources with this schedule will get applied, but they won't get applied again between 2 and 4 because they will have already run once that day, and they won't get applied outside that schedule because they will be outside the scheduled range. Puppet automatically creates a schedule for each of the valid periods with the same name as that period (e.g., hourly and daily). Additionally, a schedule named `puppet` is created and used as the default, with the following attributes: schedule { 'puppet': period => hourly, repeat => 2, } This will cause resources to be applied every 30 minutes by default. EOT apply_to_all newparam(:name) do desc <<-EOT The name of the schedule. This name is used to retrieve the schedule when assigning it to an object: schedule { 'daily': period => daily, range => "2 - 4", } exec { "/usr/bin/apt-get update": schedule => 'daily', } EOT isnamevar end newparam(:range) do desc <<-EOT The earliest and latest that a resource can be applied. This is always a hyphen-separated range within a 24 hour period, and hours must be specified in numbers between 0 and 23, inclusive. Minutes and seconds can optionally be provided, using the normal colon as a separator. For instance: schedule { 'maintenance': range => "1:30 - 4:30", } This is mostly useful for restricting certain resources to being applied in maintenance windows or during off-peak hours. Multiple ranges can be applied in array context. As a convenience when specifying ranges, you may cross midnight (e.g.: range => "22:00 - 04:00"). EOT # This is lame; properties all use arrays as values, but parameters don't. # That's going to hurt eventually. validate do |values| values = [values] unless values.is_a?(Array) values.each { |value| unless value.is_a?(String) and value =~ /\d+(:\d+){0,2}\s*-\s*\d+(:\d+){0,2}/ self.fail "Invalid range value '#{value}'" end } end munge do |values| values = [values] unless values.is_a?(Array) ret = [] values.each { |value| range = [] # Split each range value into a hour, minute, second triad value.split(/\s*-\s*/).each { |val| # Add the values as an array. range << val.split(":").collect { |n| n.to_i } } self.fail "Invalid range #{value}" if range.length != 2 # Make sure the hours are valid [range[0][0], range[1][0]].each do |n| raise ArgumentError, "Invalid hour '#{n}'" if n < 0 or n > 23 end [range[0][1], range[1][1]].each do |n| raise ArgumentError, "Invalid minute '#{n}'" if n and (n < 0 or n > 59) end ret << range } # Now our array of arrays ret end def match?(previous, now) # The lowest-level array is of the hour, minute, second triad # then it's an array of two of those, to present the limits # then it's array of those ranges @value = [@value] unless @value[0][0].is_a?(Array) @value.each do |value| limits = value.collect do |range| ary = [now.year, now.month, now.day, range[0]] if range[1] ary << range[1] else ary << now.min end if range[2] ary << range[2] else ary << now.sec end time = Time.local(*ary) unless time.hour == range[0] self.devfail( "Incorrectly converted time: #{time}: #{time.hour} vs #{range[0]}" ) end time end unless limits[0] < limits[1] self.info( "Assuming upper limit should be that time the next day" ) # Find midnight between the two days. Adding one second # to the end of the day is easier than dealing with dates. ary = limits[0].to_a ary[0] = 59 ary[1] = 59 ary[2] = 23 midnight = Time.local(*ary)+1 # If it is currently between the range start and midnight # we consider that a successful match. - return true if now.between?(limits[0], midnight) + if now.between?(limits[0], midnight) + # We have to check the weekday match here as it is special-cased + # to support day-spanning ranges. + if @resource[:weekday] + return false unless @resource[:weekday].has_key?(now.wday) + end + return true + end # If we didn't match between the starting time and midnight # we must now move our midnight back 24 hours and try # between the new midnight (24 hours prior) and the # ending time. midnight -= 86400 # Now we compare the current time between midnight and the # end time. - return true if now.between?(midnight, limits[1]) + if now.between?(midnight, limits[1]) + # This case is the reason weekday matching is special cased + # in the range parameter. If we match a range that has spanned + # past midnight we want to match against the weekday when the range + # started, not when it currently is. + if @resource[:weekday] + return false unless @resource[:weekday].has_key?((now - 86400).wday) + end + return true + end # If neither of the above matched then we don't match the # range schedule. return false end + # Check to see if a weekday parameter was specified and, if so, + # do we match it or not. If we fail we can stop here. + # This is required because spanning ranges forces us to check + # weekday within the range parameter. + if @resource[:weekday] + return false unless @resource[:weekday].has_key?(now.wday) + end + return true if now.between?(*limits) end # Else, return false, since our current time isn't between # any valid times false end end newparam(:periodmatch) do desc "Whether periods should be matched by number (e.g., the two times are in the same hour) or by distance (e.g., the two times are 60 minutes apart)." newvalues(:number, :distance) defaultto :distance end newparam(:period) do desc <<-EOT The period of repetition for a resource. The default is for a resource to get applied every time Puppet runs. Note that the period defines how often a given resource will get applied but not when; if you would like to restrict the hours that a given resource can be applied (e.g., only at night during a maintenance window), then use the `range` attribute. If the provided periods are not sufficient, you can provide a value to the *repeat* attribute, which will cause Puppet to schedule the affected resources evenly in the period the specified number of times. Take this schedule: schedule { 'veryoften': period => hourly, repeat => 6, } This can cause Puppet to apply that resource up to every 10 minutes. At the moment, Puppet cannot guarantee that level of repetition; that is, it can run up to every 10 minutes, but internal factors might prevent it from actually running that often (e.g., long-running Puppet runs will squash conflictingly scheduled runs). See the `periodmatch` attribute for tuning whether to match times by their distance apart or by their specific value. EOT newvalues(:hourly, :daily, :weekly, :monthly, :never) ScheduleScales = { :hourly => 3600, :daily => 86400, :weekly => 604800, :monthly => 2592000 } ScheduleMethods = { :hourly => :hour, :daily => :day, :monthly => :month, :weekly => proc do |prev, now| # Run the resource if the previous day was after this weekday (e.g., prev is wed, current is tue) # or if it's been more than a week since we ran prev.wday > now.wday or (now - prev) > (24 * 3600 * 7) end } def match?(previous, now) return false if value == :never value = self.value case @resource[:periodmatch] when :number method = ScheduleMethods[value] if method.is_a?(Proc) return method.call(previous, now) else # We negate it, because if they're equal we don't run return now.send(method) != previous.send(method) end when :distance scale = ScheduleScales[value] # If the number of seconds between the two times is greater # than the unit of time, we match. We divide the scale # by the repeat, so that we'll repeat that often within # the scale. diff = (now.to_i - previous.to_i) comparison = (scale / @resource[:repeat]) return (now.to_i - previous.to_i) >= (scale / @resource[:repeat]) end end end newparam(:repeat) do desc "How often a given resource may be applied in this schedule's `period`. Defaults to 1; must be an integer." defaultto 1 validate do |value| unless value.is_a?(Integer) or value =~ /^\d+$/ raise Puppet::Error, "Repeat must be a number" end # This implicitly assumes that 'periodmatch' is distance -- that # is, if there's no value, we assume it's a valid value. return unless @resource[:periodmatch] if value != 1 and @resource[:periodmatch] != :distance raise Puppet::Error, "Repeat must be 1 unless periodmatch is 'distance', not '#{@resource[:periodmatch]}'" end end munge do |value| value = Integer(value) unless value.is_a?(Integer) value end def match?(previous, now) true end end newparam(:weekday) do - desc "The days of the week in which the schedule should be valid. + desc <<-EOT + The days of the week in which the schedule should be valid. You may specify the full day name (Tuesday), the three character abbreviation (Tue), or a number corresponding to the day of the week where 0 is Sunday, 1 is Monday, etc. You may pass an array to specify multiple days. If not specified, the day of the week - will not be considered in the schedule." + will not be considered in the schedule. + + If you are also using a range match that spans across midnight + then this parameter will match the day that it was at the start + of the range, not necessarily the day that it is when it matches. + For example, consider this schedule: + + schedule { 'maintenance_window': + range => '22:00 - 04:00', + weekday => 'Saturday', + } + + This will match at 11 PM on Saturday and 2 AM on Sunday, but not + at 2 AM on Saturday. + EOT validate do |values| values = [values] unless values.is_a?(Array) values.each { |value| unless value.is_a?(String) and (value =~ /^[0-6]$/ or value =~ /^(Mon|Tues?|Wed(?:nes)?|Thu(?:rs)?|Fri|Sat(?:ur)?|Sun)(day)?$/i) raise ArgumentError, "%s is not a valid day of the week" % value end } end weekdays = { 'sun' => 0, 'mon' => 1, 'tue' => 2, 'wed' => 3, 'thu' => 4, 'fri' => 5, 'sat' => 6, } munge do |values| values = [values] unless values.is_a?(Array) ret = {} values.each { |value| if value =~ /^[0-6]$/ index = value.to_i else index = weekdays[value[0,3].downcase] end ret[index] = true } ret end def match?(previous, now) + # Special case weekday matching with ranges to a no-op here. + # If the ranges span days then we can't simply match the current + # weekday, as we want to match the weekday as it was when the range + # started. As a result, all of that logic is in range, not here. + return true if @resource[:range] + return true if value.has_key?(now.wday) false end end def self.instances [] end def self.mkdefaultschedules result = [] Puppet.debug "Creating default schedules" result << self.new( :name => "puppet", :period => :hourly, :repeat => "2" ) # And then one for every period @parameters.find { |p| p.name == :period }.value_collection.values.each { |value| result << self.new( :name => value.to_s, :period => value ) } result end def match?(previous = nil, now = nil) # If we've got a value, then convert it to a Time instance previous &&= Time.at(previous) now ||= Time.now # Pull them in order self.class.allattrs.each { |param| if @parameters.include?(param) and @parameters[param].respond_to?(:match?) return false unless @parameters[param].match?(previous, now) end } # If we haven't returned false, then return true; in other words, # any provided schedules need to all match true end end end diff --git a/spec/unit/type/schedule_spec.rb b/spec/unit/type/schedule_spec.rb index b3ee1dadb..01e3abe23 100755 --- a/spec/unit/type/schedule_spec.rb +++ b/spec/unit/type/schedule_spec.rb @@ -1,535 +1,535 @@ #!/usr/bin/env rspec require 'spec_helper' module ScheduleTesting def diff(unit, incr, method, count) diff = Time.now.to_i.send(method, incr * count) Time.at(diff) end def day(method, count) diff(:hour, 3600 * 24, method, count) end def hour(method, count) diff(:hour, 3600, method, count) end def min(method, count) diff(:min, 60, method, count) end end describe Puppet::Type.type(:schedule) do before :each do Puppet[:ignoreschedules] = false @schedule = Puppet::Type.type(:schedule).new(:name => "testing") end describe Puppet::Type.type(:schedule) do include ScheduleTesting it "should apply to device" do @schedule.must be_appliable_to_device end it "should apply to host" do @schedule.must be_appliable_to_host end it "should default to :distance for period-matching" do @schedule[:periodmatch].must == :distance end it "should default to a :repeat of 1" do @schedule[:repeat].must == 1 end it "should never match when the period is :never" do @schedule[:period] = :never @schedule.must_not be_match end end describe Puppet::Type.type(:schedule), "when producing default schedules" do include ScheduleTesting %w{hourly daily weekly monthly never}.each do |period| period = period.to_sym it "should produce a #{period} schedule with the period set appropriately" do schedules = Puppet::Type.type(:schedule).mkdefaultschedules schedules.find { |s| s[:name] == period.to_s and s[:period] == period }.must be_instance_of(Puppet::Type.type(:schedule)) end end it "should produce a schedule named puppet with a period of hourly and a repeat of 2" do schedules = Puppet::Type.type(:schedule).mkdefaultschedules schedules.find { |s| s[:name] == "puppet" and s[:period] == :hourly and s[:repeat] == 2 }.must be_instance_of(Puppet::Type.type(:schedule)) end end describe Puppet::Type.type(:schedule), "when matching ranges" do include ScheduleTesting before do Time.stubs(:now).returns(Time.local(2011, "may", 23, 11, 0, 0)) end it "should match when the start time is before the current time and the end time is after the current time" do @schedule[:range] = "10:59:50 - 11:00:10" @schedule.must be_match end it "should not match when the start time is after the current time" do @schedule[:range] = "11:00:05 - 11:00:10" @schedule.must_not be_match end it "should not match when the end time is previous to the current time" do @schedule[:range] = "10:59:50 - 10:59:55" @schedule.must_not be_match end it "should not match the current time fails between an array of ranges" do @schedule[:range] = ["4-6", "20-23"] @schedule.must_not be_match end it "should match the lower array of ranges" do @schedule[:range] = ["9-11", "14-16"] @schedule.must be_match end it "should match the upper array of ranges" do @schedule[:range] = ["4-6", "11-12"] @schedule.must be_match end end describe Puppet::Type.type(:schedule), "when matching ranges spanning days, day 1" do include ScheduleTesting before do # Test with the current time at a month's end boundary to ensure we are # advancing the day properly when we push the ending limit out a day. # For example, adding 1 to 31 would throw an error instead of advancing # the date. Time.stubs(:now).returns(Time.local(2011, "mar", 31, 22, 30, 0)) end - it "should match when the start time is before the current time and the end time is after the current time" do + it "should match when the start time is before current time and the end time is the following day" do @schedule[:range] = "22:00:00 - 02:00:00" @schedule.must be_match end it "should not match when the current time is outside the range" do @schedule[:range] = "23:30:00 - 21:00:00" @schedule.must_not be_match end end describe Puppet::Type.type(:schedule), "when matching ranges spanning days, day 2" do include ScheduleTesting before do # Test with the current time at a month's end boundary to ensure we are # advancing the day properly when we push the ending limit out a day. # For example, adding 1 to 31 would throw an error instead of advancing # the date. Time.stubs(:now).returns(Time.local(2011, "mar", 31, 1, 30, 0)) end - it "should match when the start time is before the current time and the end time is after the current time" do + it "should match when the start time is the day before the current time and the end time is after the current time" do @schedule[:range] = "22:00:00 - 02:00:00" @schedule.must be_match end it "should not match when the start time is after the current time" do @schedule[:range] = "02:00:00 - 00:30:00" @schedule.must_not be_match end it "should not match when the end time is before the current time" do @schedule[:range] = "22:00:00 - 01:00:00" @schedule.must_not be_match end end describe Puppet::Type.type(:schedule), "when matching hourly by distance" do include ScheduleTesting before do @schedule[:period] = :hourly @schedule[:periodmatch] = :distance Time.stubs(:now).returns(Time.local(2011, "may", 23, 11, 0, 0)) end it "should match when the previous time was an hour ago" do @schedule.must be_match(hour("-", 1)) end it "should not match when the previous time was now" do @schedule.must_not be_match(Time.now) end it "should not match when the previous time was 59 minutes ago" do @schedule.must_not be_match(min("-", 59)) end end describe Puppet::Type.type(:schedule), "when matching daily by distance" do include ScheduleTesting before do @schedule[:period] = :daily @schedule[:periodmatch] = :distance Time.stubs(:now).returns(Time.local(2011, "may", 23, 11, 0, 0)) end it "should match when the previous time was one day ago" do @schedule.must be_match(day("-", 1)) end it "should not match when the previous time is now" do @schedule.must_not be_match(Time.now) end it "should not match when the previous time was 23 hours ago" do @schedule.must_not be_match(hour("-", 23)) end end describe Puppet::Type.type(:schedule), "when matching weekly by distance" do include ScheduleTesting before do @schedule[:period] = :weekly @schedule[:periodmatch] = :distance Time.stubs(:now).returns(Time.local(2011, "may", 23, 11, 0, 0)) end it "should match when the previous time was seven days ago" do @schedule.must be_match(day("-", 7)) end it "should not match when the previous time was now" do @schedule.must_not be_match(Time.now) end it "should not match when the previous time was six days ago" do @schedule.must_not be_match(day("-", 6)) end end describe Puppet::Type.type(:schedule), "when matching monthly by distance" do include ScheduleTesting before do @schedule[:period] = :monthly @schedule[:periodmatch] = :distance Time.stubs(:now).returns(Time.local(2011, "may", 23, 11, 0, 0)) end it "should match when the previous time was 32 days ago" do @schedule.must be_match(day("-", 32)) end it "should not match when the previous time was now" do @schedule.must_not be_match(Time.now) end it "should not match when the previous time was 27 days ago" do @schedule.must_not be_match(day("-", 27)) end end describe Puppet::Type.type(:schedule), "when matching hourly by number" do include ScheduleTesting before do @schedule[:period] = :hourly @schedule[:periodmatch] = :number end it "should match if the times are one minute apart and the current minute is 0" do current = Time.utc(2008, 1, 1, 0, 0, 0) previous = Time.utc(2007, 12, 31, 23, 59, 0) Time.stubs(:now).returns(current) @schedule.must be_match(previous) end it "should not match if the times are 59 minutes apart and the current minute is 59" do current = Time.utc(2009, 2, 1, 12, 59, 0) previous = Time.utc(2009, 2, 1, 12, 0, 0) Time.stubs(:now).returns(current) @schedule.must_not be_match(previous) end end describe Puppet::Type.type(:schedule), "when matching daily by number" do include ScheduleTesting before do @schedule[:period] = :daily @schedule[:periodmatch] = :number end it "should match if the times are one minute apart and the current minute and hour are 0" do current = Time.utc(2010, "nov", 7, 0, 0, 0) # Now set the previous time to one minute before that previous = current - 60 Time.stubs(:now).returns(current) @schedule.must be_match(previous) end it "should not match if the times are 23 hours and 58 minutes apart and the current hour is 23 and the current minute is 59" do # Reset the previous time to 00:00:00 previous = Time.utc(2010, "nov", 7, 0, 0, 0) # Set the current time to 23:59 now = previous + (23 * 3600) + (59 * 60) Time.stubs(:now).returns(now) @schedule.must_not be_match(previous) end end describe Puppet::Type.type(:schedule), "when matching weekly by number" do include ScheduleTesting before do @schedule[:period] = :weekly @schedule[:periodmatch] = :number end it "should match if the previous time is prior to the most recent Sunday" do now = Time.utc(2010, "nov", 11, 0, 0, 0) # Thursday Time.stubs(:now).returns(now) previous = Time.utc(2010, "nov", 6, 23, 59, 59) # Sat @schedule.must be_match(previous) end it "should not match if the previous time is after the most recent Saturday" do now = Time.utc(2010, "nov", 11, 0, 0, 0) # Thursday Time.stubs(:now).returns(now) previous = Time.utc(2010, "nov", 7, 0, 0, 0) # Sunday @schedule.must_not be_match(previous) end end describe Puppet::Type.type(:schedule), "when matching monthly by number" do include ScheduleTesting before do @schedule[:period] = :monthly @schedule[:periodmatch] = :number end it "should match when the previous time is prior to the first day of this month" do now = Time.utc(2010, "nov", 8, 00, 59, 59) Time.stubs(:now).returns(now) previous = Time.utc(2010, "oct", 31, 23, 59, 59) @schedule.must be_match(previous) end it "should not match when the previous time is after the last day of last month" do now = Time.utc(2010, "nov", 8, 00, 59, 59) Time.stubs(:now).returns(now) previous = Time.utc(2010, "nov", 1, 0, 0, 0) @schedule.must_not be_match(previous) end end describe Puppet::Type.type(:schedule), "when matching with a repeat greater than one" do include ScheduleTesting before do @schedule[:period] = :daily @schedule[:repeat] = 2 Time.stubs(:now).returns(Time.local(2011, "may", 23, 11, 0, 0)) end it "should fail if the periodmatch is 'number'" do @schedule[:periodmatch] = :number proc { @schedule[:repeat] = 2 }.must raise_error(Puppet::Error) end it "should match if the previous run was further away than the distance divided by the repeat" do previous = Time.now - (3600 * 13) @schedule.must be_match(previous) end it "should not match if the previous run was closer than the distance divided by the repeat" do previous = Time.now - (3600 * 11) @schedule.must_not be_match(previous) end end describe Puppet::Type.type(:schedule), "when matching days of the week" do include ScheduleTesting before do # 2011-05-23 is a Monday Time.stubs(:now).returns(Time.local(2011, "may", 23, 11, 0, 0)) end it "should raise an error if the weekday is 'Someday'" do proc { @schedule[:weekday] = "Someday" }.should raise_error(Puppet::Error) end it "should raise an error if the weekday is '7'" do proc { @schedule[:weekday] = "7" }.should raise_error(Puppet::Error) end it "should accept all full weekday names as valid values" do proc { @schedule[:weekday] = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'] }.should_not raise_error end it "should accept all short weekday names as valid values" do proc { @schedule[:weekday] = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'] }.should_not raise_error end it "should match if the weekday is 'Monday'" do @schedule[:weekday] = "Monday" @schedule.match?.should be_true end it "should match if the weekday is 'Mon'" do @schedule[:weekday] = "Mon" @schedule.match?.should be_true end it "should match if the weekday is '1'" do @schedule[:weekday] = "1" @schedule.match?.should be_true end it "should not match if the weekday is Tuesday" do @schedule[:weekday] = "Tuesday" @schedule.should_not be_match end it "should match if weekday is ['Sun', 'Mon']" do @schedule[:weekday] = ["Sun", "Mon"] @schedule.match?.should be_true end it "should not match if weekday is ['Sun', 'Tue']" do @schedule[:weekday] = ["Sun", "Tue"] @schedule.should_not be_match end it "should match if the weekday is 'Monday'" do @schedule[:weekday] = "Monday" @schedule.match?.should be_true end it "should match if the weekday is 'Mon'" do @schedule[:weekday] = "Mon" @schedule.match?.should be_true end it "should match if the weekday is '1'" do @schedule[:weekday] = "1" @schedule.match?.should be_true end it "should not match if the weekday is Tuesday" do @schedule[:weekday] = "Tuesday" @schedule.should_not be_match end it "should match if weekday is ['Sun', 'Mon']" do @schedule[:weekday] = ["Sun", "Mon"] @schedule.match?.should be_true end end describe Puppet::Type.type(:schedule), "when matching days of week and ranges spanning days, day 1" do include ScheduleTesting before do # Test with ranges and days-of-week both set. 2011-03-31 was a Thursday. Time.stubs(:now).returns(Time.local(2011, "mar", 31, 22, 30, 0)) end it "should match when the range and day of week matches" do @schedule[:range] = "22:00:00 - 02:00:00" @schedule[:weekday] = "Thursday" @schedule.must be_match end it "should not match when the range doesn't match even if the day-of-week matches" do @schedule[:range] = "23:30:00 - 21:00:00" @schedule[:weekday] = "Thursday" @schedule.must_not be_match end it "should not match when day-of-week doesn't match even if the range matches (1 day later)" do @schedule[:range] = "22:00:00 - 01:00:00" @schedule[:weekday] = "Friday" @schedule.must_not be_match end it "should not match when day-of-week doesn't match even if the range matches (1 day earlier)" do @schedule[:range] = "22:00:00 - 01:00:00" @schedule[:weekday] = "Wednesday" @schedule.must_not be_match end end describe Puppet::Type.type(:schedule), "when matching days of week and ranges spanning days, day 2" do include ScheduleTesting before do # 2011-03-31 was a Thursday. As the end-time of a day spanning match, that means # we need to match on Wednesday. Time.stubs(:now).returns(Time.local(2011, "mar", 31, 1, 30, 0)) end it "should match when the range matches and the day of week should match" do @schedule[:range] = "22:00:00 - 02:00:00" @schedule[:weekday] = "Wednesday" @schedule.must be_match end it "should not match when the range does not match and the day of week should match" do @schedule[:range] = "22:00:00 - 01:00:00" @schedule[:weekday] = "Thursday" @schedule.must_not be_match end it "should not match when the range matches but the day-of-week does not (1 day later)" do @schedule[:range] = "22:00:00 - 02:00:00" @schedule[:weekday] = "Thursday" @schedule.must_not be_match end it "should not match when the range matches but the day-of-week does not (1 day later)" do @schedule[:range] = "22:00:00 - 02:00:00" @schedule[:weekday] = "Tuesday" @schedule.must_not be_match end end end