diff --git a/lib/puppet/transaction/event_manager.rb b/lib/puppet/transaction/event_manager.rb index c9338da8b..4a630baae 100644 --- a/lib/puppet/transaction/event_manager.rb +++ b/lib/puppet/transaction/event_manager.rb @@ -1,147 +1,159 @@ require 'puppet/transaction' # This class stores, routes, and responds to events generated while evaluating # a transaction. # # @api private class Puppet::Transaction::EventManager # @!attribute [r] transaction # @return [Puppet::Transaction] The transaction associated with this event manager. attr_reader :transaction # @!attribute [r] events # @todo Determine if this instance variable is used for anything aside from testing. # @return [Array] A list of events that can be # handled by the target resouce. Events that cannot be handled by the # target resource will be discarded. attr_reader :events def initialize(transaction) @transaction = transaction @event_queues = {} @events = [] end def relationship_graph transaction.relationship_graph end # Respond to any queued events for this resource. def process_events(resource) restarted = false queued_events(resource) do |callback, events| r = process_callback(resource, callback, events) restarted ||= r end if restarted queue_events(resource, [resource.event(:name => :restarted, :status => "success")]) transaction.resource_status(resource).restarted = true end end # Queues events for other resources to respond to. All of these events have # to be from the same resource. # # @param resource [Puppet::Type] The resource generating the given events # @param events [Array] All events generated by this resource # @return [void] def queue_events(resource, events) #@events += events # Do some basic normalization so we're not doing so many # graph queries for large sets of events. events.inject({}) do |collection, event| collection[event.name] ||= [] collection[event.name] << event collection end.collect do |name, list| # It doesn't matter which event we use - they all have the same source # and name here. event = list[0] # Collect the targets of any subscriptions to those events. We pass # the parent resource in so it will override the source in the events, # since eval_generated children can't have direct relationships. received = (event.name != :restarted) relationship_graph.matching_edges(event, resource).each do |edge| received ||= true unless edge.target.is_a?(Puppet::Type.type(:whit)) next unless method = edge.callback next unless edge.target.respond_to?(method) queue_events_for_resource(resource, edge.target, method, list) end @events << event if received queue_events_for_resource(resource, resource, :refresh, [event]) if resource.self_refresh? and ! resource.deleting? end dequeue_events_for_resource(resource, :refresh) if events.detect { |e| e.invalidate_refreshes } end def dequeue_events_for_resource(target, callback) target.info "Unscheduling #{callback} on #{target}" @event_queues[target][callback] = {} if @event_queues[target] end def queue_events_for_resource(source, target, callback, events) whit = Puppet::Type.type(:whit) # The message that a resource is refreshing the completed-whit for its own class # is extremely counter-intuitive. Basically everything else is easy to understand, # if you suppress the whit-lookingness of the whit resources refreshing_c_whit = target.is_a?(whit) && target.name =~ /^completed_/ if refreshing_c_whit source.debug "The container #{target} will propagate my #{callback} event" else source.info "Scheduling #{callback} of #{target}" end @event_queues[target] ||= {} @event_queues[target][callback] ||= [] @event_queues[target][callback].concat(events) end def queued_events(resource) return unless callbacks = @event_queues[resource] callbacks.each do |callback, events| yield callback, events unless events.empty? end end private + # Should the callback for this resource be invoked? + # @param resource [Puppet::Type] The resource to be refreshed + # @param events [Array] A list of events + # associated with this callback and resource. + # @return [true, false] Whether the callback should be run. + def process_callback?(resource, events) + !(events.all? { |e| e.status == "noop" } || resource.noop?) + end + # Processes callbacks for a given resource. # # @param resource [Puppet::Type] The resource receiving the callback. # @param callback [Symbol] The name of the callback method that will be invoked. # @param events [Array] A list of events # associated with this callback and resource. # @return [true, false] Whether the callback was successfully run. def process_callback(resource, callback, events) - process_noop_events(resource, callback, events) and return false unless events.detect { |e| e.status != "noop" } + if !process_callback?(resource, events) + process_noop_events(resource, callback, events) + return false + end + resource.send(callback) if not resource.is_a?(Puppet::Type.type(:whit)) resource.notice "Triggered '#{callback}' from #{events.length} events" end return true rescue => detail resource.err "Failed to call #{callback}: #{detail}" transaction.resource_status(resource).failed_to_restart = true resource.log_exception(detail) return false end def process_noop_events(resource, callback, events) resource.notice "Would have triggered '#{callback}' from #{events.length} events" # And then add an event for it. queue_events(resource, [resource.event(:status => "noop", :name => :noop_restart)]) - true # so the 'and if' works end end diff --git a/spec/integration/transaction_spec.rb b/spec/integration/transaction_spec.rb index 35557b8f2..4106efc76 100755 --- a/spec/integration/transaction_spec.rb +++ b/spec/integration/transaction_spec.rb @@ -1,362 +1,382 @@ #! /usr/bin/env ruby require 'spec_helper' require 'puppet/transaction' describe Puppet::Transaction do include PuppetSpec::Files before do Puppet::Util::Storage.stubs(:store) end def mk_catalog(*resources) catalog = Puppet::Resource::Catalog.new(Puppet::Node.new("mynode")) resources.each { |res| catalog.add_resource res } catalog end def usr_bin_touch(path) Puppet.features.microsoft_windows? ? "#{ENV['windir']}/system32/cmd.exe /c \"type NUL >> \"#{path}\"\"" : "/usr/bin/touch #{path}" end def touch(path) Puppet.features.microsoft_windows? ? "cmd.exe /c \"type NUL >> \"#{path}\"\"" : "touch #{path}" end it "should not apply generated resources if the parent resource fails" do catalog = Puppet::Resource::Catalog.new resource = Puppet::Type.type(:file).new :path => make_absolute("/foo/bar"), :backup => false catalog.add_resource resource child_resource = Puppet::Type.type(:file).new :path => make_absolute("/foo/bar/baz"), :backup => false resource.expects(:eval_generate).returns([child_resource]) transaction = Puppet::Transaction.new(catalog, nil, Puppet::Graph::RandomPrioritizer.new) resource.expects(:retrieve).raises "this is a failure" resource.stubs(:err) child_resource.expects(:retrieve).never transaction.evaluate end it "should not apply virtual resources" do catalog = Puppet::Resource::Catalog.new resource = Puppet::Type.type(:file).new :path => make_absolute("/foo/bar"), :backup => false resource.virtual = true catalog.add_resource resource transaction = Puppet::Transaction.new(catalog, nil, Puppet::Graph::RandomPrioritizer.new) resource.expects(:evaluate).never transaction.evaluate end it "should apply exported resources" do catalog = Puppet::Resource::Catalog.new path = tmpfile("exported_files") resource = Puppet::Type.type(:file).new :path => path, :backup => false, :ensure => :file resource.exported = true catalog.add_resource resource catalog.apply Puppet::FileSystem.exist?(path).should be_true end it "should not apply virtual exported resources" do catalog = Puppet::Resource::Catalog.new resource = Puppet::Type.type(:file).new :path => make_absolute("/foo/bar"), :backup => false resource.exported = true resource.virtual = true catalog.add_resource resource transaction = Puppet::Transaction.new(catalog, nil, Puppet::Graph::RandomPrioritizer.new) resource.expects(:evaluate).never transaction.evaluate end it "should not apply device resources on normal host" do catalog = Puppet::Resource::Catalog.new resource = Puppet::Type.type(:interface).new :name => "FastEthernet 0/1" catalog.add_resource resource transaction = Puppet::Transaction.new(catalog, nil, Puppet::Graph::RandomPrioritizer.new) transaction.for_network_device = false transaction.expects(:apply).never.with(resource, nil) transaction.evaluate transaction.resource_status(resource).should be_skipped end it "should not apply host resources on device" do catalog = Puppet::Resource::Catalog.new resource = Puppet::Type.type(:file).new :path => make_absolute("/foo/bar"), :backup => false catalog.add_resource resource transaction = Puppet::Transaction.new(catalog, nil, Puppet::Graph::RandomPrioritizer.new) transaction.for_network_device = true transaction.expects(:apply).never.with(resource, nil) transaction.evaluate transaction.resource_status(resource).should be_skipped end it "should apply device resources on device" do catalog = Puppet::Resource::Catalog.new resource = Puppet::Type.type(:interface).new :name => "FastEthernet 0/1" catalog.add_resource resource transaction = Puppet::Transaction.new(catalog, nil, Puppet::Graph::RandomPrioritizer.new) transaction.for_network_device = true transaction.expects(:apply).with(resource, nil) transaction.evaluate transaction.resource_status(resource).should_not be_skipped end it "should apply resources appliable on host and device on a device" do catalog = Puppet::Resource::Catalog.new resource = Puppet::Type.type(:schedule).new :name => "test" catalog.add_resource resource transaction = Puppet::Transaction.new(catalog, nil, Puppet::Graph::RandomPrioritizer.new) transaction.for_network_device = true transaction.expects(:apply).with(resource, nil) transaction.evaluate transaction.resource_status(resource).should_not be_skipped end # Verify that one component requiring another causes the contained # resources in the requiring component to get refreshed. it "should propagate events from a contained resource through its container to its dependent container's contained resources" do transaction = nil file = Puppet::Type.type(:file).new :path => tmpfile("event_propagation"), :ensure => :present execfile = File.join(tmpdir("exec_event"), "exectestingness2") exec = Puppet::Type.type(:exec).new :command => touch(execfile), :path => ENV['PATH'] catalog = mk_catalog(file) fcomp = Puppet::Type.type(:component).new(:name => "Foo[file]") catalog.add_resource fcomp catalog.add_edge(fcomp, file) ecomp = Puppet::Type.type(:component).new(:name => "Foo[exec]") catalog.add_resource ecomp catalog.add_resource exec catalog.add_edge(ecomp, exec) ecomp[:subscribe] = Puppet::Resource.new(:foo, "file") exec[:refreshonly] = true exec.expects(:refresh) catalog.apply end # Make sure that multiple subscriptions get triggered. it "should propagate events to all dependent resources" do path = tmpfile("path") file1 = tmpfile("file1") file2 = tmpfile("file2") file = Puppet::Type.type(:file).new( :path => path, :ensure => "file" ) exec1 = Puppet::Type.type(:exec).new( :path => ENV["PATH"], :command => touch(file1), :refreshonly => true, :subscribe => Puppet::Resource.new(:file, path) ) exec2 = Puppet::Type.type(:exec).new( :path => ENV["PATH"], :command => touch(file2), :refreshonly => true, :subscribe => Puppet::Resource.new(:file, path) ) catalog = mk_catalog(file, exec1, exec2) catalog.apply Puppet::FileSystem.exist?(file1).should be_true Puppet::FileSystem.exist?(file2).should be_true end + it "does not refresh resources that have 'noop => true'" do + path = tmpfile("path") + + notify = Puppet::Type.type(:notify).new( + :name => "trigger", + :notify => Puppet::Resource.new(:exec, "noop exec") + ) + + noop_exec = Puppet::Type.type(:exec).new( + :name => "noop exec", + :path => ENV["PATH"], + :command => touch(path), + :noop => true + ) + + catalog = mk_catalog(notify, noop_exec) + catalog.apply + Puppet::FileSystem.exist?(path).should be_false + end + it "should apply no resources whatsoever if a pre_run_check fails" do path = tmpfile("path") file = Puppet::Type.type(:file).new( :path => path, :ensure => "file" ) notify = Puppet::Type.type(:notify).new( :title => "foo" ) notify.expects(:pre_run_check).raises(Puppet::Error, "fail for testing") catalog = mk_catalog(file, notify) catalog.apply Puppet::FileSystem.exist?(path).should_not be_true end it "should not let one failed refresh result in other refreshes failing" do path = tmpfile("path") newfile = tmpfile("file") file = Puppet::Type.type(:file).new( :path => path, :ensure => "file" ) exec1 = Puppet::Type.type(:exec).new( :path => ENV["PATH"], :command => touch(File.expand_path("/this/cannot/possibly/exist")), :logoutput => true, :refreshonly => true, :subscribe => file, :title => "one" ) exec2 = Puppet::Type.type(:exec).new( :path => ENV["PATH"], :command => touch(newfile), :logoutput => true, :refreshonly => true, :subscribe => [file, exec1], :title => "two" ) exec1.stubs(:err) catalog = mk_catalog(file, exec1, exec2) catalog.apply Puppet::FileSystem.exist?(newfile).should be_true end it "should still trigger skipped resources" do catalog = mk_catalog catalog.add_resource(*Puppet::Type.type(:schedule).mkdefaultschedules) Puppet[:ignoreschedules] = false file = Puppet::Type.type(:file).new( :name => tmpfile("file"), :ensure => "file", :backup => false ) fname = tmpfile("exec") exec = Puppet::Type.type(:exec).new( :name => touch(fname), :path => Puppet.features.microsoft_windows? ? "#{ENV['windir']}/system32" : "/usr/bin:/bin", :schedule => "monthly", :subscribe => Puppet::Resource.new("file", file.name) ) catalog.add_resource(file, exec) # Run it once catalog.apply Puppet::FileSystem.exist?(fname).should be_true # Now remove it, so it can get created again Puppet::FileSystem.unlink(fname) file[:content] = "some content" catalog.apply Puppet::FileSystem.exist?(fname).should be_true # Now remove it, so it can get created again Puppet::FileSystem.unlink(fname) # And tag our exec exec.tag("testrun") # And our file, so it runs file.tag("norun") Puppet[:tags] = "norun" file[:content] = "totally different content" catalog.apply Puppet::FileSystem.exist?(fname).should be_true end it "should not attempt to evaluate resources with failed dependencies" do exec = Puppet::Type.type(:exec).new( :command => "#{File.expand_path('/bin/mkdir')} /this/path/cannot/possibly/exist", :title => "mkdir" ) file1 = Puppet::Type.type(:file).new( :title => "file1", :path => tmpfile("file1"), :require => exec, :ensure => :file ) file2 = Puppet::Type.type(:file).new( :title => "file2", :path => tmpfile("file2"), :require => file1, :ensure => :file ) catalog = mk_catalog(exec, file1, file2) catalog.apply Puppet::FileSystem.exist?(file1[:path]).should be_false Puppet::FileSystem.exist?(file2[:path]).should be_false end it "should not trigger subscribing resources on failure" do file1 = tmpfile("file1") file2 = tmpfile("file2") create_file1 = Puppet::Type.type(:exec).new( :command => usr_bin_touch(file1) ) exec = Puppet::Type.type(:exec).new( :command => "#{File.expand_path('/bin/mkdir')} /this/path/cannot/possibly/exist", :title => "mkdir", :notify => create_file1 ) create_file2 = Puppet::Type.type(:exec).new( :command => usr_bin_touch(file2), :subscribe => exec ) catalog = mk_catalog(exec, create_file1, create_file2) catalog.apply Puppet::FileSystem.exist?(file1).should be_false Puppet::FileSystem.exist?(file2).should be_false end # #801 -- resources only checked in noop should be rescheduled immediately. it "should immediately reschedule noop resources" do Puppet::Type.type(:schedule).mkdefaultschedules resource = Puppet::Type.type(:notify).new(:name => "mymessage", :noop => true) catalog = Puppet::Resource::Catalog.new catalog.add_resource resource trans = catalog.apply trans.resource_harness.should be_scheduled(resource) end end diff --git a/spec/unit/transaction/event_manager_spec.rb b/spec/unit/transaction/event_manager_spec.rb index 84a8b93a6..5d53be3fa 100755 --- a/spec/unit/transaction/event_manager_spec.rb +++ b/spec/unit/transaction/event_manager_spec.rb @@ -1,310 +1,340 @@ #! /usr/bin/env ruby require 'spec_helper' require 'puppet/transaction/event_manager' describe Puppet::Transaction::EventManager do include PuppetSpec::Files describe "at initialization" do it "should require a transaction" do Puppet::Transaction::EventManager.new("trans").transaction.should == "trans" end end it "should delegate its relationship graph to the transaction" do transaction = stub 'transaction' manager = Puppet::Transaction::EventManager.new(transaction) transaction.expects(:relationship_graph).returns "mygraph" manager.relationship_graph.should == "mygraph" end describe "when queueing events" do before do @manager = Puppet::Transaction::EventManager.new(@transaction) @resource = Puppet::Type.type(:file).new :path => make_absolute("/my/file") @graph = stub 'graph', :matching_edges => [], :resource => @resource @manager.stubs(:relationship_graph).returns @graph @event = Puppet::Transaction::Event.new(:name => :foo, :resource => @resource) end it "should store all of the events in its event list" do @event2 = Puppet::Transaction::Event.new(:name => :bar, :resource => @resource) @manager.queue_events(@resource, [@event, @event2]) @manager.events.should include(@event) @manager.events.should include(@event2) end it "should queue events for the target and callback of any matching edges" do edge1 = stub("edge1", :callback => :c1, :source => stub("s1"), :target => stub("t1", :c1 => nil)) edge2 = stub("edge2", :callback => :c2, :source => stub("s2"), :target => stub("t2", :c2 => nil)) @graph.expects(:matching_edges).with { |event, resource| event == @event }.returns [edge1, edge2] @manager.expects(:queue_events_for_resource).with(@resource, edge1.target, edge1.callback, [@event]) @manager.expects(:queue_events_for_resource).with(@resource, edge2.target, edge2.callback, [@event]) @manager.queue_events(@resource, [@event]) end it "should queue events for the changed resource if the resource is self-refreshing and not being deleted" do @graph.stubs(:matching_edges).returns [] @resource.expects(:self_refresh?).returns true @resource.expects(:deleting?).returns false @manager.expects(:queue_events_for_resource).with(@resource, @resource, :refresh, [@event]) @manager.queue_events(@resource, [@event]) end it "should not queue events for the changed resource if the resource is not self-refreshing" do @graph.stubs(:matching_edges).returns [] @resource.expects(:self_refresh?).returns false @resource.stubs(:deleting?).returns false @manager.expects(:queue_events_for_resource).never @manager.queue_events(@resource, [@event]) end it "should not queue events for the changed resource if the resource is being deleted" do @graph.stubs(:matching_edges).returns [] @resource.expects(:self_refresh?).returns true @resource.expects(:deleting?).returns true @manager.expects(:queue_events_for_resource).never @manager.queue_events(@resource, [@event]) end it "should ignore edges that don't have a callback" do edge1 = stub("edge1", :callback => :nil, :source => stub("s1"), :target => stub("t1", :c1 => nil)) @graph.expects(:matching_edges).returns [edge1] @manager.expects(:queue_events_for_resource).never @manager.queue_events(@resource, [@event]) end it "should ignore targets that don't respond to the callback" do edge1 = stub("edge1", :callback => :c1, :source => stub("s1"), :target => stub("t1")) @graph.expects(:matching_edges).returns [edge1] @manager.expects(:queue_events_for_resource).never @manager.queue_events(@resource, [@event]) end it "should dequeue events for the changed resource if an event with invalidate_refreshes is processed" do @event2 = Puppet::Transaction::Event.new(:name => :foo, :resource => @resource, :invalidate_refreshes => true) @graph.stubs(:matching_edges).returns [] @manager.expects(:dequeue_events_for_resource).with(@resource, :refresh) @manager.queue_events(@resource, [@event, @event2]) end end describe "when queueing events for a resource" do before do @transaction = stub 'transaction' @manager = Puppet::Transaction::EventManager.new(@transaction) end it "should do nothing if no events are queued" do @manager.queued_events(stub("target")) { |callback, events| raise "should never reach this" } end it "should yield the callback and events for each callback" do target = stub("target") 2.times do |i| @manager.queue_events_for_resource(stub("source", :info => nil), target, "callback#{i}", ["event#{i}"]) end @manager.queued_events(target) { |callback, events| } end it "should use the source to log that it's scheduling a refresh of the target" do target = stub("target") source = stub 'source' source.expects(:info) @manager.queue_events_for_resource(source, target, "callback", ["event"]) @manager.queued_events(target) { |callback, events| } end end describe "when processing events for a given resource" do before do @transaction = Puppet::Transaction.new(Puppet::Resource::Catalog.new, nil, nil) @manager = Puppet::Transaction::EventManager.new(@transaction) @manager.stubs(:queue_events) @resource = Puppet::Type.type(:file).new :path => make_absolute("/my/file") @event = Puppet::Transaction::Event.new(:name => :event, :resource => @resource) end it "should call the required callback once for each set of associated events" do @manager.expects(:queued_events).with(@resource).multiple_yields([:callback1, [@event]], [:callback2, [@event]]) @resource.expects(:callback1) @resource.expects(:callback2) @manager.process_events(@resource) end it "should set the 'restarted' state on the resource status" do @manager.expects(:queued_events).with(@resource).yields(:callback1, [@event]) @resource.stubs(:callback1) @manager.process_events(@resource) @transaction.resource_status(@resource).should be_restarted end it "should queue a 'restarted' event generated by the resource" do @manager.expects(:queued_events).with(@resource).yields(:callback1, [@event]) @resource.stubs(:callback1) @resource.expects(:event).with(:name => :restarted, :status => "success").returns "myevent" @manager.expects(:queue_events).with(@resource, ["myevent"]) @manager.process_events(@resource) end it "should log that it restarted" do @manager.expects(:queued_events).with(@resource).yields(:callback1, [@event]) @resource.stubs(:callback1) @resource.expects(:notice).with { |msg| msg.include?("Triggered 'callback1'") } @manager.process_events(@resource) end describe "and the events include a noop event and at least one non-noop event" do before do @event.stubs(:status).returns "noop" @event2 = Puppet::Transaction::Event.new(:name => :event, :resource => @resource) @event2.status = "success" @manager.expects(:queued_events).with(@resource).yields(:callback1, [@event, @event2]) end it "should call the callback" do @resource.expects(:callback1) @manager.process_events(@resource) end end describe "and the events are all noop events" do before do @event.stubs(:status).returns "noop" @resource.stubs(:event).returns(Puppet::Transaction::Event.new) @manager.expects(:queued_events).with(@resource).yields(:callback1, [@event]) end it "should log" do @resource.expects(:notice).with { |msg| msg.include?("Would have triggered 'callback1'") } @manager.process_events(@resource) end it "should not call the callback" do @resource.expects(:callback1).never @manager.process_events(@resource) end it "should queue a new noop event generated from the resource" do event = Puppet::Transaction::Event.new @resource.expects(:event).with(:status => "noop", :name => :noop_restart).returns event @manager.expects(:queue_events).with(@resource, [event]) @manager.process_events(@resource) end end + describe "and the resource has noop set to true" do + before do + @event.stubs(:status).returns "success" + @resource.stubs(:event).returns(Puppet::Transaction::Event.new) + @resource.stubs(:noop?).returns(true) + @manager.expects(:queued_events).with(@resource).yields(:callback1, [@event]) + end + + it "should log" do + @resource.expects(:notice).with { |msg| msg.include?("Would have triggered 'callback1'") } + + @manager.process_events(@resource) + end + + it "should not call the callback" do + @resource.expects(:callback1).never + + @manager.process_events(@resource) + end + + it "should queue a new noop event generated from the resource" do + event = Puppet::Transaction::Event.new + @resource.expects(:event).with(:status => "noop", :name => :noop_restart).returns event + @manager.expects(:queue_events).with(@resource, [event]) + + @manager.process_events(@resource) + end + end + + describe "and the callback fails" do before do @resource.expects(:callback1).raises "a failure" @resource.stubs(:err) @manager.expects(:queued_events).yields(:callback1, [@event]) end it "should log but not fail" do @resource.expects(:err) lambda { @manager.process_events(@resource) }.should_not raise_error end it "should set the 'failed_restarts' state on the resource status" do @manager.process_events(@resource) @transaction.resource_status(@resource).should be_failed_to_restart end it "should not queue a 'restarted' event" do @manager.expects(:queue_events).never @manager.process_events(@resource) end it "should set the 'restarted' state on the resource status" do @manager.process_events(@resource) @transaction.resource_status(@resource).should_not be_restarted end end end describe "when queueing then processing events for a given resource" do before do @transaction = Puppet::Transaction.new(Puppet::Resource::Catalog.new, nil, nil) @manager = Puppet::Transaction::EventManager.new(@transaction) @resource = Puppet::Type.type(:file).new :path => make_absolute("/my/file") @target = Puppet::Type.type(:file).new :path => make_absolute("/your/file") @graph = stub 'graph' @graph.stubs(:matching_edges).returns [] @graph.stubs(:matching_edges).with(anything, @resource).returns [stub('edge', :target => @target, :callback => :refresh)] @manager.stubs(:relationship_graph).returns @graph @event = Puppet::Transaction::Event.new(:name => :notify, :resource => @target) @event2 = Puppet::Transaction::Event.new(:name => :service_start, :resource => @target, :invalidate_refreshes => true) end it "should succeed when there's no invalidated event" do @manager.queue_events(@target, [@event2]) end describe "and the events were dequeued/invalidated" do before do @resource.expects(:info).with { |msg| msg.include?("Scheduling refresh") } @target.expects(:info).with { |msg| msg.include?("Unscheduling") } end it "should not run an event or log" do @target.expects(:notice).with { |msg| msg.include?("Would have triggered 'refresh'") }.never @target.expects(:refresh).never @manager.queue_events(@resource, [@event]) @manager.queue_events(@target, [@event2]) @manager.process_events(@resource) @manager.process_events(@target) end end end end