From 5bfcbcba66cd257acf792835c35b79122cf514b7 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 9 Dec 2011 13:40:49 -0800 Subject: [PATCH] Remove step stuff. Failed experiment for now. Too radical for point release. --- lib/vagrant/action/multistep.rb | 186 ---------------- lib/vagrant/action/step.rb | 133 ------------ test/unit/vagrant/action/multistep_test.rb | 233 --------------------- test/unit/vagrant/action/step_test.rb | 112 ---------- 4 files changed, 664 deletions(-) delete mode 100644 lib/vagrant/action/multistep.rb delete mode 100644 lib/vagrant/action/step.rb delete mode 100644 test/unit/vagrant/action/multistep_test.rb delete mode 100644 test/unit/vagrant/action/step_test.rb diff --git a/lib/vagrant/action/multistep.rb b/lib/vagrant/action/multistep.rb deleted file mode 100644 index 3f8bd66c3..000000000 --- a/lib/vagrant/action/multistep.rb +++ /dev/null @@ -1,186 +0,0 @@ -module Vagrant - class Action - class MultiStep < Step - # This class is used as a placeholder to represent a parameter that - # needs to be replaced at runtime. For example: A step might take a - # parameter B that is outputted as A from a previous step. An instance - # of this class can represent that the previous A should be remapped - # to this new B. - class Param - attr_reader :name - attr_reader :variable - - def initialize(name, variable) - @name = name - @variable = variable - end - end - - # Represents a remapping that comes from the group inputs. - class GroupInput < Param; end - - # Represents a remapping that comes from the output of a previous step. - class StepOutput < Param; end - - def initialize - @step_names = [] - @steps = {} - end - - # This returns a custom object that represents an input parameter - # given to this group. - # - # @param [Symbol] key Parameter name of the input to use from the group. - # @return [GroupInput] A `param` type that can be used for remappings. - def input(key) - return GroupInput.new(:global, key) - end - - # This returns a custom object that represents an output parameter - # from another step in this group. - # - # @param [Object] name Name of the step. This is either an explicit name - # like a symbol or the class for the step if it is unique. - # @param [Symbol] output The output variable name from the step. - # @return [StepOutput] A `param` type that can be used for remappings. - def output(name, output) - return StepOutput.new(name, output) - end - - def step(step_class, *extra_inputs) - # Determine the name for this step. - step_name = nil - if step_class.is_a?(Symbol) - step_name = step_class - step_class = extra_inputs.shift - else - step_name = step_class - end - - if @steps.has_key?(step_name) - raise NameError, "Step with name #{step_name} already exists." - elsif !step_class.is_a?(Class) - raise ArgumentError, "Step class must be a class." - end - - # Get the options hash and set the defaults - maps = {} - maps = extra_inputs.pop if extra_inputs.last.kind_of?(Hash) - - # Go over each extra input and handle the mapping. This is typically - # syntactic sugar. - extra_inputs.each do |direct| - if direct.is_a?(Symbol) - # Symbols are assumed to be inputs to this group - direct = input(direct) - end - - maps[direct] = direct.variable - end - - # Take the missing inputs and map them together. For example - # the input of ':a' maps directly to the previous output of ':a' - (step_class.inputs - maps.values).each do |input| - maps[input] = input - end - - # Turn the pure symbols into mapping from last steps - maps.keys.each do |from| - if from.kind_of?(Symbol) - new_from = nil - if @step_names.last.nil? - new_from = input(from) - else - new_from = output(@step_names.last, from) - end - - maps[new_from] = maps.delete(from) - end - end - - # Verify that all the mappings are correctly satisfied. - maps.each do |from, to| - if !mapping_satisfied?(step_class, from, to) - raise ArgumentError, "Mapping from #{from.variable} to #{to} fails." - end - end - - # Append the step - @step_names << step_name - @steps[step_name] = [step_class, maps] - end - - def call(params=nil) - params ||= {} - - # Instantiate all the steps - steps = @step_names.map do |name| - step_class, maps = @steps[name] - [name, step_class.new, maps] - end - - # For each step, call it with proper inputs, using the output - # of that call as inputs to the next. - entered_steps = [] - step_outputs = {} - result = steps.inject(params) do |inputs, data| - name, step, mappings = data - - # If we have inputs to remap, remap them. - mappings.each do |from, to| - if from.kind_of?(GroupInput) - # Group inputs get their data from the initial parameters given - # to this group. - inputs[to] = params[from.variable] - elsif from.kind_of?(StepOutput) - # Step outputs get their data from a previous step's output. - inputs[to] = step_outputs[from.name][from.variable] - else - raise ArgumentError, "Unknown type of remapping." - end - end - - # Call the actual step, using the results for the next - # iteration. - entered_steps << step - begin - step_outputs[name] = step.call_enter(inputs) - rescue Exception => e - entered_steps.reverse.each do |s| - s.call_exit(e) - end - - raise - end - - # Return a shallow dup of the results - step_outputs[name].dup - end - - entered_steps.reverse.each do |s| - s.call_exit(nil) - end - - result - end - - protected - - def mapping_satisfied?(step, from, to) - # If the step inputs don't contain to then we fail - return false if !step.inputs.include?(to) - - if from.kind_of?(GroupInput) - # We assume that inputs from the group are satisfied since - # it has to come in. - return true - elsif from.kind_of?(StepOutput) - # Verify that the outputs of the step exist to map. - return @steps[from.name][0].outputs.include?(from.variable) - end - - true - end - end - end -end diff --git a/lib/vagrant/action/step.rb b/lib/vagrant/action/step.rb deleted file mode 100644 index 3fc606f0a..000000000 --- a/lib/vagrant/action/step.rb +++ /dev/null @@ -1,133 +0,0 @@ -module Vagrant - class Action - # A step is a callable action that Vagrant uses to build up - # more complex actions sequences. - # - # A step must specify the inputs it requires and the outputs - # it will return, and can implement two methods: `enter` and - # `exit` (both are optional, but a step is useless without - # at least one). - # - # The inputs are guaranteed to be ready only by the time - # `enter` is called and are available as instance variables. - # `enter` is called first. - # - # `exit` is called at some point after `enter` (the exact time - # is not guarateed) and is given one parameter: `error` which - # is non-nil if an exception was raised at some point during - # or after `enter` was called. The return value of `exit` does - # nothing. - class Step - class UnsatisfiedRequirementsError < RuntimeError; end - - # The keys given to this will be required by the step. Each key - # should be a symbol. - def self.input(*keys) - inputs.concat(keys) - end - - # The values that this step provides. Each key should be a symbol. - def self.output(*keys) - outputs.concat(keys) - end - - # This is the array of required keys. - def self.inputs - @inputs ||= [] - end - - # The array of keys that are provided by this Step. - def self.outputs - @outputs ||= [] - end - - # Validates that the output matches the specification provided, and - # raises a RuntimeError if it does not. - def self.process_output(value) - # The return value must be a Hash, so we just coerce it to that. - value = {} if !value.kind_of?(Hash) - - # Verify that we have all the outputs - missing = outputs - value.keys - raise RuntimeError, "Missing output keys: #{missing}" if !missing.empty? - - return value - end - - # This calls the step with the given parameters, and returns a hash - # of the outputs. - # - # Additional options may be provided via the options hash at the end. - # - # @param [Hash] params Parameters for the step. - # @param [Hash] options Options hash - # @option options [Symbol] :method Method to execute. - # @option options [Boolean] :validate_output Whether to validate the - # output or not. - # @return [Hash] Output - def call(params={}) - # Call the actual implementation - results = nil - begin - results = call_enter(params) - rescue UnsatisfiedRequirementsError - # This doesn't get an `exit` call called since enter - # was never even called in this case. - raise - rescue Exception => e - call_exit(e) - raise - end - - # No exception occurred if we reach this point. Call exit. - call_exit(nil) - - # Return the final results - results - end - - # This method will only call the `enter` method for the step. - # - # The parameters given here will be validated as the inputs for - # the step and used to call `enter`. The results of `enter` will - # be validated as the outputs and returned. - # - # @param [Hash] inputs - # @return [Hash] - def call_enter(inputs={}) - # Set and validate the inputs - set_inputs(inputs) - - # Call the actual enter call - results = nil - results = send(:enter) if respond_to?(:enter) - - # Validate the outputs if it is enabled and the list of configured - # outputs is not empty. - self.class.process_output(results) - end - - # This method will call `exit` with the given error. - def call_exit(error) - send(:exit, error) if respond_to?(:exit) - end - - protected - - # Sets the parameters for the step. - # - # This will raise an exception if all the `requires` are not - # properly met. Otherwise, the parameters are set as instance variables - # on this instance. - def set_inputs(params) - inputs = self.class.inputs - remaining = inputs - params.keys - raise UnsatisfiedRequirementsError, "Missing parameters: #{remaining}" if !remaining.empty? - - inputs.each do |key| - instance_variable_set("@#{key}", params[key]) - end - end - end - end -end diff --git a/test/unit/vagrant/action/multistep_test.rb b/test/unit/vagrant/action/multistep_test.rb deleted file mode 100644 index 87c6a268f..000000000 --- a/test/unit/vagrant/action/multistep_test.rb +++ /dev/null @@ -1,233 +0,0 @@ -require File.expand_path("../../../base", __FILE__) - -describe Vagrant::Action::MultiStep do - it "should compose a series of steps" do - step_A = Class.new(Vagrant::Action::Step) do - input :obj - output :obj - - def enter - @obj << "A" - return :obj => @obj - end - end - - step_B = Class.new(Vagrant::Action::Step) do - input :obj - output :result - - def enter - return :result => (@obj << "B") - end - end - - obj = [] - - ms = described_class.new - ms.step step_A - ms.step step_B - ms.call(:obj => obj).should == { :result => ["A", "B"] } - end - - it "should allow for custom inputs to pass to specific steps" do - step_A = Class.new(Vagrant::Action::Step) do - def enter - # Do nothing. - end - end - - step_B = Class.new(Vagrant::Action::Step) do - input :obj - - def enter - @obj << "B" - end - end - - obj = [] - - ms = described_class.new - ms.step step_A - ms.step step_B, :obj - ms.call(:obj => obj) - - obj.should == ["B"] - end - - it "should be able to remap input names" do - step_A = Class.new(Vagrant::Action::Step) do - output :foo - - def enter - return :foo => "A" - end - end - - step_B = Class.new(Vagrant::Action::Step) do - input :from - output :value - - def enter - return :value => @from - end - end - - obj = [] - - ms = described_class.new - ms.step step_A - ms.step step_B, :foo => :from - ms.call.should == { :value => "A" } - end - - it "should be able to reference variables from steps before other steps" do - step_A = Class.new(Vagrant::Action::Step) do - output :foo - - def enter - return :foo => 10 - end - end - - step_B = Class.new(Vagrant::Action::Step) do - input :from - output :value - - def enter - return :value => @from - end - end - - step_C = Class.new(Vagrant::Action::Step) do - input :number - output :value - - def enter - return :value => @number * 2 - end - end - - obj = [] - - g = described_class.new - g.step step_A - g.step step_B, :foo => :from - g.step step_C, g.output(step_A, :foo) => :number - g.call.should == { :value => 20 } - end - - it "should error if multiple steps of the same class are given without explicit names" do - step_A = Class.new(Vagrant::Action::Step) - - g = described_class.new - g.step step_A - expect { g.step step_A }.to raise_error(NameError) - end - - it "should not error if multiple steps of the same class are given with custom names" do - step_A = Class.new(Vagrant::Action::Step) - - g = described_class.new - g.step step_A - expect { g.step :another, step_A }.to_not raise_error - end - - it "should error if a step is not a class" do - g = described_class.new - expect { g.step :foo }.to raise_error(ArgumentError) - end - - it "should not allow a step that doesn't have all inputs satisfied" do - step_A = Class.new(Vagrant::Action::Step) do - output :output_A - end - - step_B = Class.new(Vagrant::Action::Step) do - input :input_B - end - - g = described_class.new - g.step step_A - expect { g.step step_B }.to raise_error(ArgumentError) - end - - it "should not allow remapping from outputs that don't exist" do - step_A = Class.new(Vagrant::Action::Step) do - output :output_A - end - - step_B = Class.new(Vagrant::Action::Step) do - input :input_B - end - - g = described_class.new - g.step step_A - expect { g.step step_B, :output_B => :input_B }.to raise_error(ArgumentError) - end - - it "should not allow remapping to inputs that don't exist" do - step_A = Class.new(Vagrant::Action::Step) do - input :input_A - end - - g = described_class.new - expect { g.step step_A, g.input(:foo) => :input_B }.to raise_error(ArgumentError) - end - - it "should call the enter methods in order, and the exit in reverse order" do - step = Class.new(Vagrant::Action::Step) do - input :key - input :data - output :data - - def enter - @data << @key - return :data => @data - end - - def exit(error) - @data << @key - end - end - - g = described_class.new - g.step step - g.step :two, step, g.input(:key2) => :key - g.step :three, step, g.input(:key3) => :key - result = g.call(:data => [], :key => "1", :key2 => "2", :key3 => "3") - result[:data].should == %W[1 2 3 3 2 1] - end - - it "should halt the steps and call exit with the error if an error occurs" do - step = Class.new(Vagrant::Action::Step) do - input :key - input :data - output :data - - def enter - @data << @key - raise Exception, "E" if @data.last == "2" - return :data => @data - end - - def exit(error) - prefix = error ? error.message : "" - @data << "#{prefix}#{@key}" - end - end - - g = described_class.new - g.step step - g.step :two, step, g.input(:key2) => :key - g.step :three, step, g.input(:key3) => :key - - # Run the actual steps - data = [] - expect do - result = g.call(:data => data, :key => "1", :key2 => "2", :key3 => "3") - end.to raise_error(Exception) - - # Verify the result hit the methods in the proper order - data.should == %W[1 2 E2 E1] - end -end diff --git a/test/unit/vagrant/action/step_test.rb b/test/unit/vagrant/action/step_test.rb deleted file mode 100644 index f74e21905..000000000 --- a/test/unit/vagrant/action/step_test.rb +++ /dev/null @@ -1,112 +0,0 @@ -require File.expand_path("../../../base", __FILE__) - -describe Vagrant::Action::Step do - describe "call_enter" do - it "calls enter with inputs and returns the outputs" do - step_class = Class.new(described_class) do - input :in - output :out - - def enter - return :out => @in * 2 - end - end - - step_class.new.call_enter(:in => 12).should == { :out => 24 } - end - - it "raises an exception if not all required parameters are given" do - step_class = Class.new(described_class) do - input :foo - end - - expect { step_class.new.call_enter }.to raise_error(Vagrant::Action::Step::UnsatisfiedRequirementsError) - end - - it "return an empty hash if no outputs are specified" do - step_class = Class.new(described_class) do - def enter - return 12 - end - end - - step_class.new.call_enter.should == {} - end - - it "raises an exception if missing outputs" do - step_class = Class.new(described_class) do - output :foo - - def enter - return :bar => 12 - end - end - - expect { step_class.new.call_enter }.to raise_error(RuntimeError) - end - end - - describe "call_exit" do - it "should simply call the `exit` method with the given argument" do - step_class = Class.new(described_class) do - def exit(error) - raise RuntimeError, error - end - end - - expect { step_class.new.call_exit(7) }.to raise_error(RuntimeError) - end - end - - describe "calling" do - it "calls enter then exit" do - step_class = Class.new(described_class) do - input :obj - - def enter - @obj << "enter" - end - - def exit(error) - @obj << "exit" - end - end - - obj = [] - step_class.new.call(:obj => obj) - obj.should == ["enter", "exit"] - end - - it "calls exit with nil if no exception occurred" do - step_class = Class.new(described_class) do - input :obj - - def exit(error) - @obj << error - end - end - - obj = [] - step_class.new.call(:obj => obj) - obj.should == [nil] - end - - it "calls exit with an exception if it occurred" do - step_class = Class.new(described_class) do - input :obj - - def enter - raise RuntimeError, "foo" - end - - def exit(error) - @obj << error - end - end - - obj = [] - expect { step_class.new.call(:obj => obj) }.to raise_error(RuntimeError) - obj[0].should be_kind_of(RuntimeError) - end - end -end