diff --git a/lib/vagrant/actions/base.rb b/lib/vagrant/actions/base.rb index 4104984c8..ad7fbc777 100644 --- a/lib/vagrant/actions/base.rb +++ b/lib/vagrant/actions/base.rb @@ -46,6 +46,18 @@ module Vagrant # Do lots of stuff here # @vm.invoke_callback(:after_oven, "more", "than", "one", "option") end + + # This method is only called if some exception occurs in the chain + # of actions. If an exception is raised in any action in the current + # chain, then every action part of that chain has {#rescue} called + # before raising the exception further. This method should be used to + # perform any cleanup necessary in the face of errors. + # + # **Warning:** Since this method is called when an exception is already + # raised, be _extra careful_ when implementing this method to handle + # all your own exceptions, otherwise it'll mask the initially raised + # exception. + def rescue(exception); end end class ActionException < Exception; end diff --git a/lib/vagrant/actions/runner.rb b/lib/vagrant/actions/runner.rb index f7d4c3321..48bbbfed7 100644 --- a/lib/vagrant/actions/runner.rb +++ b/lib/vagrant/actions/runner.rb @@ -38,10 +38,20 @@ module Vagrant # Call the prepare method on each once its # initialized, then call the execute! method - [:prepare, :execute!].each do |method| - actions.each do |action| - action.send(method) + begin + [:prepare, :execute!].each do |method| + actions.each do |action| + action.send(method) + end end + rescue Exception => e + # Run the rescue code to do any emergency cleanup + actions.each do |action| + action.rescue(e) + end + + # Finally, reraise the exception + raise end # Clear the actions diff --git a/test/vagrant/actions/runner_test.rb b/test/vagrant/actions/runner_test.rb index 7f4952718..451522b2f 100644 --- a/test/vagrant/actions/runner_test.rb +++ b/test/vagrant/actions/runner_test.rb @@ -66,22 +66,11 @@ class ActionRunnerTest < Test::Unit::TestCase end end - context "actions" do + context "adding actions" do setup do @runner = Vagrant::Actions::Runner.new end - should "setup actions to be an array" do - assert_nil @runner.instance_variable_get(:@actions) - actions = @runner.actions - assert actions.is_a?(Array) - assert actions.equal?(@runner.actions) - end - - should "be empty initially" do - assert @runner.actions.empty? - end - should "initialize the action when added" do action_klass = mock("action_class") action_inst = mock("action_inst") @@ -95,6 +84,34 @@ class ActionRunnerTest < Test::Unit::TestCase action_klass.expects(:new).with(@runner, "foo", "bar").once @runner.add_action(action_klass, "foo", "bar") end + end + + context "class method execute" do + should "run actions on class method execute!" do + vm = mock("vm") + execute_seq = sequence("execute_seq") + Vagrant::Actions::Runner.expects(:new).returns(vm).in_sequence(execute_seq) + vm.expects(:add_action).with("foo").in_sequence(execute_seq) + vm.expects(:execute!).once.in_sequence(execute_seq) + + Vagrant::Actions::Runner.execute!("foo") + end + + should "forward arguments to add_action on class method execute!" do + vm = mock("vm") + execute_seq = sequence("execute_seq") + Vagrant::Actions::Runner.expects(:new).returns(vm).in_sequence(execute_seq) + vm.expects(:add_action).with("foo", "bar", "baz").in_sequence(execute_seq) + vm.expects(:execute!).once.in_sequence(execute_seq) + + Vagrant::Actions::Runner.execute!("foo", "bar", "baz") + end + end + + context "instance method execute" do + setup do + @runner = Vagrant::Actions::Runner.new + end should "clear the actions and run a single action if given to execute!" do action = mock("action") @@ -138,24 +155,48 @@ class ActionRunnerTest < Test::Unit::TestCase @runner.execute! end - should "run actions on class method execute!" do - vm = mock("vm") - execute_seq = sequence("execute_seq") - Vagrant::VM.expects(:new).returns(vm).in_sequence(execute_seq) - vm.expects(:add_action).with("foo").in_sequence(execute_seq) - vm.expects(:execute!).once.in_sequence(execute_seq) + context "exceptions" do + setup do + @actions = [mock_fake_action, mock_fake_action] + @actions.each { |a| @runner.actions << a } - Vagrant::VM.execute!("foo") + @exception = Exception.new + end + + should "call #rescue on each action if an exception is raised during execute!" do + @actions.each do |a| + a.expects(:rescue).with(@exception).once + end + + @actions[0].stubs(:execute!).raises(@exception) + assert_raises(Exception) { @runner.execute! } + end + + should "call #rescue on each action if an exception is raised during prepare" do + @actions.each do |a| + a.expects(:rescue).with(@exception).once + end + + @actions[0].stubs(:prepare).raises(@exception) + assert_raises(Exception) { @runner.execute! } + end + end + end + + context "actions" do + setup do + @runner = Vagrant::Actions::Runner.new end - should "forward arguments to add_action on class method execute!" do - vm = mock("vm") - execute_seq = sequence("execute_seq") - Vagrant::VM.expects(:new).returns(vm).in_sequence(execute_seq) - vm.expects(:add_action).with("foo", "bar", "baz").in_sequence(execute_seq) - vm.expects(:execute!).once.in_sequence(execute_seq) + should "setup actions to be an array" do + assert_nil @runner.instance_variable_get(:@actions) + actions = @runner.actions + assert actions.is_a?(Array) + assert actions.equal?(@runner.actions) + end - Vagrant::VM.execute!("foo", "bar", "baz") + should "be empty initially" do + assert @runner.actions.empty? end end end