diff --git a/lib/puppet/parser/ast.rb b/lib/puppet/parser/ast.rb index 122b4dd7a..8918d22e9 100644 --- a/lib/puppet/parser/ast.rb +++ b/lib/puppet/parser/ast.rb @@ -1,142 +1,142 @@ # the parent class for all of our syntactical objects require 'puppet' require 'puppet/util/autoload' require 'puppet/file_collection/lookup' # The base class for all of the objects that make up the parse trees. # Handles things like file name, line #, and also does the initialization # for all of the parameters of all of the child objects. class Puppet::Parser::AST # Do this so I don't have to type the full path in all of the subclasses AST = Puppet::Parser::AST include Puppet::FileCollection::Lookup include Puppet::Util::Errors include Puppet::Util::MethodHelper include Puppet::Util::Docs attr_accessor :parent, :scope def inspect "( #{self.class} #{self.to_s} #{@children.inspect} )" end # don't fetch lexer comment by default def use_docs self.class.use_docs end # allow our subclass to specify they want documentation class << self attr_accessor :use_docs def associates_doc self.use_docs = true end end # Does this ast object set something? If so, it gets evaluated first. def self.settor? if defined?(@settor) @settor else false end end # Evaluate the current object. Just a stub method, since the subclass # should override this method. # of the contained children and evaluates them in turn, returning a # list of all of the collected values, rejecting nil values def evaluate(*options) raise Puppet::DevError, "Did not override #evaluate in #{self.class}" end # Throw a parse error. def parsefail(message) self.fail(Puppet::ParseError, message) end # Wrap a statemp in a reusable way so we always throw a parse error. def parsewrap exceptwrap :type => Puppet::ParseError do yield end end # The version of the evaluate method that should be called, because it # correctly handles errors. It is critical to use this method because # it can enable you to catch the error where it happens, rather than # much higher up the stack. def safeevaluate(*options) # We duplicate code here, rather than using exceptwrap, because this # is called so many times during parsing. begin return self.evaluate(*options) rescue Puppet::Error => detail raise adderrorcontext(detail) rescue => detail error = Puppet::Error.new(detail.to_s) # We can't use self.fail here because it always expects strings, # not exceptions. raise adderrorcontext(error, detail) end end # Initialize the object. Requires a hash as the argument, and # takes each of the parameters of the hash and calls the settor # method for them. This is probably pretty inefficient and should # likely be changed at some point. def initialize(args) set_options(args) end # evaluate ourselves, and match def evaluate_match(value, scope) obj = self.safeevaluate(scope) obj = obj.downcase if obj.respond_to?(:downcase) value = value.downcase if value.respond_to?(:downcase) obj = Puppet::Parser::Scope.number?(obj) || obj value = Puppet::Parser::Scope.number?(value) || value # "" == undef for case/selector/if - obj == value or (obj == "" and value == :undef) + obj == value or (obj == "" and value == :undef) or (obj == :undef and value == "") end end # And include all of the AST subclasses. require 'puppet/parser/ast/arithmetic_operator' require 'puppet/parser/ast/astarray' require 'puppet/parser/ast/asthash' require 'puppet/parser/ast/boolean_operator' require 'puppet/parser/ast/branch' require 'puppet/parser/ast/caseopt' require 'puppet/parser/ast/casestatement' require 'puppet/parser/ast/collection' require 'puppet/parser/ast/collexpr' require 'puppet/parser/ast/comparison_operator' require 'puppet/parser/ast/definition' require 'puppet/parser/ast/else' require 'puppet/parser/ast/function' require 'puppet/parser/ast/hostclass' require 'puppet/parser/ast/ifstatement' require 'puppet/parser/ast/in_operator' require 'puppet/parser/ast/leaf' require 'puppet/parser/ast/match_operator' require 'puppet/parser/ast/minus' require 'puppet/parser/ast/node' require 'puppet/parser/ast/nop' require 'puppet/parser/ast/not' require 'puppet/parser/ast/relationship' require 'puppet/parser/ast/resource' require 'puppet/parser/ast/resource_defaults' require 'puppet/parser/ast/resource_instance' require 'puppet/parser/ast/resource_override' require 'puppet/parser/ast/resource_reference' require 'puppet/parser/ast/resourceparam' require 'puppet/parser/ast/selector' require 'puppet/parser/ast/tag' require 'puppet/parser/ast/vardef' diff --git a/spec/unit/parser/ast/leaf_spec.rb b/spec/unit/parser/ast/leaf_spec.rb index d70dc6541..94b2abb09 100755 --- a/spec/unit/parser/ast/leaf_spec.rb +++ b/spec/unit/parser/ast/leaf_spec.rb @@ -1,443 +1,443 @@ -#!/usr/bin/env rspec +#!/usr/bin/env ruby -S rspec require 'spec_helper' describe Puppet::Parser::AST::Leaf do before :each do @scope = stub 'scope' @value = stub 'value' @leaf = Puppet::Parser::AST::Leaf.new(:value => @value) end it "should have a evaluate_match method" do Puppet::Parser::AST::Leaf.new(:value => "value").should respond_to(:evaluate_match) end describe "when converting to string" do it "should transform its value to string" do value = stub 'value', :is_a? => true value.expects(:to_s) Puppet::Parser::AST::Leaf.new( :value => value ).to_s end end it "should have a match method" do @leaf.should respond_to(:match) end it "should delegate match to ==" do @value.expects(:==).with("value") @leaf.match("value") end end describe Puppet::Parser::AST::FlatString do describe "when converting to string" do it "should transform its value to a quoted string" do value = stub 'value', :is_a? => true, :to_s => "ab" Puppet::Parser::AST::FlatString.new( :value => value ).to_s.should == "\"ab\"" end end end describe Puppet::Parser::AST::String do describe "when converting to string" do it "should transform its value to a quoted string" do value = stub 'value', :is_a? => true, :to_s => "ab" Puppet::Parser::AST::String.new( :value => value ).to_s.should == "\"ab\"" end it "should return a dup of its value" do value = "" Puppet::Parser::AST::String.new( :value => value ).evaluate(stub('scope')).should_not be_equal(value) end end end describe Puppet::Parser::AST::Concat do describe "when evaluating" do before :each do @scope = stub_everything 'scope' end it "should interpolate variables and concatenate their values" do one = Puppet::Parser::AST::String.new(:value => "one") one.stubs(:evaluate).returns("one ") two = Puppet::Parser::AST::String.new(:value => "two") two.stubs(:evaluate).returns(" two ") three = Puppet::Parser::AST::String.new(:value => "three") three.stubs(:evaluate).returns(" three") var = Puppet::Parser::AST::Variable.new(:value => "myvar") var.stubs(:evaluate).returns("foo") array = Puppet::Parser::AST::Variable.new(:value => "array") array.stubs(:evaluate).returns(["bar","baz"]) concat = Puppet::Parser::AST::Concat.new(:value => [one,var,two,array,three]) concat.evaluate(@scope).should == 'one foo two barbaz three' end it "should transform undef variables to empty string" do var = Puppet::Parser::AST::Variable.new(:value => "myvar") var.stubs(:evaluate).returns(:undef) concat = Puppet::Parser::AST::Concat.new(:value => [var]) concat.evaluate(@scope).should == '' end end end describe Puppet::Parser::AST::Undef do before :each do @scope = stub 'scope' @undef = Puppet::Parser::AST::Undef.new(:value => :undef) end it "should match undef with undef" do @undef.evaluate_match(:undef, @scope).should be_true end it "should not match undef with an empty string" do - @undef.evaluate_match("", @scope).should be_false + @undef.evaluate_match("", @scope).should be_true end end describe Puppet::Parser::AST::HashOrArrayAccess do before :each do @scope = stub 'scope' end describe "when evaluating" do it "should evaluate the variable part if necessary" do @scope.stubs(:lookupvar).with { |name,options| name == 'a'}.returns(["b"]) variable = stub 'variable', :evaluate => "a" access = Puppet::Parser::AST::HashOrArrayAccess.new(:variable => variable, :key => 0 ) variable.expects(:safeevaluate).with(@scope).returns("a") access.evaluate(@scope).should == "b" end it "should evaluate the access key part if necessary" do @scope.stubs(:lookupvar).with { |name,options| name == 'a'}.returns(["b"]) index = stub 'index', :evaluate => 0 access = Puppet::Parser::AST::HashOrArrayAccess.new(:variable => "a", :key => index ) index.expects(:safeevaluate).with(@scope).returns(0) access.evaluate(@scope).should == "b" end it "should be able to return an array member" do @scope.stubs(:lookupvar).with { |name,options| name == 'a'}.returns(["val1", "val2", "val3"]) access = Puppet::Parser::AST::HashOrArrayAccess.new(:variable => "a", :key => 1 ) access.evaluate(@scope).should == "val2" end it "should be able to return an array member when index is a stringified number" do @scope.stubs(:lookupvar).with { |name,options| name == "a" }.returns(["val1", "val2", "val3"]) access = Puppet::Parser::AST::HashOrArrayAccess.new(:variable => "a", :key => "1" ) access.evaluate(@scope).should == "val2" end it "should raise an error when accessing an array with a key" do @scope.stubs(:lookupvar).with { |name,options| name == "a"}.returns(["val1", "val2", "val3"]) access = Puppet::Parser::AST::HashOrArrayAccess.new(:variable => "a", :key => "get_me_the_second_element_please" ) lambda { access.evaluate(@scope) }.should raise_error end it "should be able to return :undef for an unknown array index" do @scope.stubs(:lookupvar).with { |name,options| name == 'a'}.returns(["val1", "val2", "val3"]) access = Puppet::Parser::AST::HashOrArrayAccess.new(:variable => "a", :key => 6 ) access.evaluate(@scope).should == :undef end it "should be able to return an hash value" do @scope.stubs(:lookupvar).with { |name,options| name == 'a'}.returns({ "key1" => "val1", "key2" => "val2", "key3" => "val3" }) access = Puppet::Parser::AST::HashOrArrayAccess.new(:variable => "a", :key => "key2" ) access.evaluate(@scope).should == "val2" end it "should be able to return :undef for unknown hash keys" do @scope.stubs(:lookupvar).with { |name,options| name == 'a'}.returns({ "key1" => "val1", "key2" => "val2", "key3" => "val3" }) access = Puppet::Parser::AST::HashOrArrayAccess.new(:variable => "a", :key => "key12" ) access.evaluate(@scope).should == :undef end it "should be able to return an hash value with a numerical key" do @scope.stubs(:lookupvar).with { |name,options| name == "a"}.returns({ "key1" => "val1", "key2" => "val2", "45" => "45", "key3" => "val3" }) access = Puppet::Parser::AST::HashOrArrayAccess.new(:variable => "a", :key => "45" ) access.evaluate(@scope).should == "45" end it "should raise an error if the variable lookup didn't return an hash or an array" do @scope.stubs(:lookupvar).with { |name,options| name == "a"}.returns("I'm a string") access = Puppet::Parser::AST::HashOrArrayAccess.new(:variable => "a", :key => "key2" ) lambda { access.evaluate(@scope) }.should raise_error end it "should raise an error if the variable wasn't in the scope" do @scope.stubs(:lookupvar).with { |name,options| name == 'a'}.returns(nil) access = Puppet::Parser::AST::HashOrArrayAccess.new(:variable => "a", :key => "key2" ) lambda { access.evaluate(@scope) }.should raise_error end it "should return a correct string representation" do access = Puppet::Parser::AST::HashOrArrayAccess.new(:variable => "a", :key => "key2" ) access.to_s.should == '$a[key2]' end it "should work with recursive hash access" do @scope.stubs(:lookupvar).with { |name,options| name == 'a'}.returns({ "key" => { "subkey" => "b" }}) access1 = Puppet::Parser::AST::HashOrArrayAccess.new(:variable => "a", :key => "key") access2 = Puppet::Parser::AST::HashOrArrayAccess.new(:variable => access1, :key => "subkey") access2.evaluate(@scope).should == 'b' end it "should work with interleaved array and hash access" do @scope.stubs(:lookupvar).with { |name,options| name == 'a'}.returns({ "key" => [ "a" , "b" ]}) access1 = Puppet::Parser::AST::HashOrArrayAccess.new(:variable => "a", :key => "key") access2 = Puppet::Parser::AST::HashOrArrayAccess.new(:variable => access1, :key => 1) access2.evaluate(@scope).should == 'b' end end describe "when assigning" do it "should add a new key and value" do scope = Puppet::Parser::Scope.new scope.setvar("a", { 'a' => 'b' }) access = Puppet::Parser::AST::HashOrArrayAccess.new(:variable => "a", :key => "b") access.assign(scope, "c" ) scope.lookupvar("a").should be_include("b") end it "should raise an error when assigning an array element with a key" do @scope.stubs(:lookupvar).with { |name,options| name == "a"}.returns([]) access = Puppet::Parser::AST::HashOrArrayAccess.new(:variable => "a", :key => "get_me_the_second_element_please" ) lambda { access.assign(@scope, "test") }.should raise_error end it "should be able to return an array member when index is a stringified number" do scope = Puppet::Parser::Scope.new scope.setvar("a", []) access = Puppet::Parser::AST::HashOrArrayAccess.new(:variable => "a", :key => "0" ) access.assign(scope, "val2") scope.lookupvar("a").should == ["val2"] end it "should raise an error when trying to overwrite an hash value" do @scope.stubs(:lookupvar).with { |name,options| name == "a" }.returns({ "key" => [ "a" , "b" ]}) access = Puppet::Parser::AST::HashOrArrayAccess.new(:variable => "a", :key => "key") lambda { access.assign(@scope, "test") }.should raise_error end end end describe Puppet::Parser::AST::Regex do before :each do @scope = stub 'scope', :ephemeral_from => true end describe "when initializing" do it "should create a Regexp with its content when value is not a Regexp" do Regexp.expects(:new).with("/ab/") Puppet::Parser::AST::Regex.new :value => "/ab/" end it "should not create a Regexp with its content when value is a Regexp" do value = Regexp.new("/ab/") Regexp.expects(:new).with("/ab/").never Puppet::Parser::AST::Regex.new :value => value end end describe "when evaluating" do it "should return self" do val = Puppet::Parser::AST::Regex.new :value => "/ab/" val.evaluate(@scope).should === val end end describe "when evaluate_match" do before :each do @value = stub 'regex' @value.stubs(:match).with("value").returns(true) Regexp.stubs(:new).returns(@value) @regex = Puppet::Parser::AST::Regex.new :value => "/ab/" end it "should issue the regexp match" do @value.expects(:match).with("value") @regex.evaluate_match("value", @scope) end it "should not downcase the paramater value" do @value.expects(:match).with("VaLuE") @regex.evaluate_match("VaLuE", @scope) end it "should set ephemeral scope vars if there is a match" do @scope.expects(:ephemeral_from).with(true, nil, nil) @regex.evaluate_match("value", @scope) end it "should return the match to the caller" do @value.stubs(:match).with("value").returns(:match) @scope.stubs(:ephemeral_from) @regex.evaluate_match("value", @scope) end end it "should match undef to the empty string" do regex = Puppet::Parser::AST::Regex.new(:value => "^$") regex.evaluate_match(:undef, @scope).should be_true end it "should not match undef to a non-empty string" do regex = Puppet::Parser::AST::Regex.new(:value => '\w') regex.evaluate_match(:undef, @scope).should be_false end it "should match a string against a string" do regex = Puppet::Parser::AST::Regex.new(:value => '\w') regex.evaluate_match('foo', @scope).should be_true end it "should return the regex source with to_s" do regex = stub 'regex' Regexp.stubs(:new).returns(regex) val = Puppet::Parser::AST::Regex.new :value => "/ab/" regex.expects(:source) val.to_s end it "should delegate match to the underlying regexp match method" do regex = Regexp.new("/ab/") val = Puppet::Parser::AST::Regex.new :value => regex regex.expects(:match).with("value") val.match("value") end end describe Puppet::Parser::AST::Variable do before :each do @scope = stub 'scope' @var = Puppet::Parser::AST::Variable.new(:value => "myvar", :file => 'my.pp', :line => 222) end it "should lookup the variable in scope" do @scope.expects(:lookupvar).with { |name,options| name == "myvar" }.returns(:myvalue) @var.safeevaluate(@scope).should == :myvalue end it "should pass the source location to lookupvar" do @scope.expects(:lookupvar).with { |name,options| name == "myvar" and options[:file] == 'my.pp' and options[:line] == 222 }.returns(:myvalue) @var.safeevaluate(@scope).should == :myvalue end it "should return undef if the variable wasn't set" do @scope.expects(:lookupvar).with { |name,options| name == "myvar" }.returns(:undefined) @var.safeevaluate(@scope).should == :undef end describe "when converting to string" do it "should transform its value to a variable" do value = stub 'value', :is_a? => true, :to_s => "myvar" Puppet::Parser::AST::Variable.new( :value => value ).to_s.should == "\$myvar" end end end describe Puppet::Parser::AST::HostName do before :each do @scope = stub 'scope' @value = stub 'value', :=~ => false @value.stubs(:to_s).returns(@value) @value.stubs(:downcase).returns(@value) @host = Puppet::Parser::AST::HostName.new( :value => @value) end it "should raise an error if hostname is not valid" do lambda { Puppet::Parser::AST::HostName.new( :value => "not an hostname!" ) }.should raise_error end it "should not raise an error if hostname is a regex" do lambda { Puppet::Parser::AST::HostName.new( :value => Puppet::Parser::AST::Regex.new(:value => "/test/") ) }.should_not raise_error end it "should stringify the value" do value = stub 'value', :=~ => false value.expects(:to_s).returns("test") Puppet::Parser::AST::HostName.new(:value => value) end it "should downcase the value" do value = stub 'value', :=~ => false value.stubs(:to_s).returns("UPCASED") host = Puppet::Parser::AST::HostName.new(:value => value) host.value == "upcased" end it "should evaluate to its value" do @host.evaluate(@scope).should == @value end it "should delegate eql? to the underlying value if it is an HostName" do @value.expects(:eql?).with("value") @host.eql?("value") end it "should delegate eql? to the underlying value if it is not an HostName" do value = stub 'compared', :is_a? => true, :value => "value" @value.expects(:eql?).with("value") @host.eql?(value) end it "should delegate hash to the underlying value" do @value.expects(:hash) @host.hash end end diff --git a/spec/unit/parser/ast_spec.rb b/spec/unit/parser/ast_spec.rb index 4d4871219..4b2e1bba1 100755 --- a/spec/unit/parser/ast_spec.rb +++ b/spec/unit/parser/ast_spec.rb @@ -1,110 +1,110 @@ -#!/usr/bin/env rspec +#!/usr/bin/env ruby -S rspec require 'spec_helper' require 'puppet/parser/ast' describe Puppet::Parser::AST do it "should use the file lookup module" do Puppet::Parser::AST.ancestors.should be_include(Puppet::FileCollection::Lookup) end it "should have a doc accessor" do ast = Puppet::Parser::AST.new({}) ast.should respond_to(:doc) end it "should have a use_docs accessor to indicate it wants documentation" do ast = Puppet::Parser::AST.new({}) ast.should respond_to(:use_docs) end [ Puppet::Parser::AST::Collection, Puppet::Parser::AST::Else, Puppet::Parser::AST::Function, Puppet::Parser::AST::IfStatement, Puppet::Parser::AST::Resource, Puppet::Parser::AST::ResourceDefaults, Puppet::Parser::AST::ResourceOverride, Puppet::Parser::AST::VarDef ].each do |k| it "#{k}.use_docs should return true" do ast = k.new({}) ast.use_docs.should be_true end end describe "when initializing" do it "should store the doc argument if passed" do ast = Puppet::Parser::AST.new(:doc => "documentation") ast.doc.should == "documentation" end end end describe 'AST Generic Child' do before :each do @value = stub 'value' class Evaluateable < Puppet::Parser::AST attr_accessor :value def safeevaluate(*options) return value end end @evaluateable = Evaluateable.new(:value => @value) @scope = stubs 'scope' end describe "when evaluate_match is called" do it "should evaluate itself" do @evaluateable.expects(:safeevaluate).with(@scope) @evaluateable.evaluate_match("value", @scope) end it "should match values by equality" do @value.expects(:==).with("value").returns(true) @evaluateable.evaluate_match("value", @scope) end it "should downcase the evaluated value if wanted" do @value.expects(:downcase).returns("value") @evaluateable.evaluate_match("value", @scope) end it "should convert values to number" do Puppet::Parser::Scope.expects(:number?).with(@value).returns(2) Puppet::Parser::Scope.expects(:number?).with("23").returns(23) @evaluateable.evaluate_match("23", @scope) end it "should compare 'numberized' values" do two = stub_everything 'two' one = stub_everything 'one' Puppet::Parser::Scope.stubs(:number?).with(@value).returns(one) Puppet::Parser::Scope.stubs(:number?).with("2").returns(two) one.expects(:==).with(two) @evaluateable.evaluate_match("2", @scope) end it "should match undef if value is an empty string" do @evaluateable.value = '' @evaluateable.evaluate_match(:undef, @scope).should be_true end it "should downcase the parameter value if wanted" do parameter = stub 'parameter' parameter.expects(:downcase).returns("value") @evaluateable.evaluate_match(parameter, @scope) end - it "should not match '' if value is undef" do + it "should match '' if value is undef" do @evaluateable.value = :undef - @evaluateable.evaluate_match('', @scope).should be_false + @evaluateable.evaluate_match('', @scope).should be_true end end end