diff --git a/lib/puppet/parser/ast.rb b/lib/puppet/parser/ast.rb index bf60c75a0..b7e324687 100644 --- a/lib/puppet/parser/ast.rb +++ b/lib/puppet/parser/ast.rb @@ -1,131 +1,130 @@ # the parent class for all of our syntactical objects require 'puppet' require 'puppet/util/autoload' # 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::Util::Errors include Puppet::Util::MethodHelper include Puppet::Util::Docs attr_accessor :parent, :scope, :file, :line, :pos 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 # Evaluate the current object. Just a stub method, since the subclass # should override this method. 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::ParseError.new(detail.to_s, nil, nil, detail) # 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) 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/lambda' require 'puppet/parser/ast/leaf' require 'puppet/parser/ast/match_operator' require 'puppet/parser/ast/method_call' 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' require 'puppet/parser/code_merger' diff --git a/lib/puppet/parser/ast/block_expression.rb b/lib/puppet/parser/ast/block_expression.rb index ac6173c39..b6b7e66b4 100644 --- a/lib/puppet/parser/ast/block_expression.rb +++ b/lib/puppet/parser/ast/block_expression.rb @@ -1,45 +1,40 @@ require 'puppet/parser/ast/branch' class Puppet::Parser::AST class BlockExpression < Branch include Enumerable # Evaluate contained expressions, produce result of the last def evaluate(scope) result = nil @children.each do |child| - # Skip things that respond to :instantiate (classes, nodes, - # and definitions), because they have already been - # instantiated. - if !child.respond_to?(:instantiate) - result = child.safeevaluate(scope) - end + result = child.safeevaluate(scope) end result end # Return a child by index. def [](index) @children[index] end def push(*ary) ary.each { |child| #Puppet.debug "adding %s(%s) of type %s to %s" % # [child, child.object_id, child.class.to_s.sub(/.+::/,''), # self.object_id] @children.push(child) } self end def sequence_with(other) Puppet::Parser::AST::BlockExpression.new(:children => self.children + other.children) end def to_s "[" + @children.collect { |c| c.to_s }.join(', ') + "]" end end end diff --git a/lib/puppet/parser/code_merger.rb b/lib/puppet/parser/code_merger.rb index 62d72dd05..e8913b88b 100644 --- a/lib/puppet/parser/code_merger.rb +++ b/lib/puppet/parser/code_merger.rb @@ -1,13 +1,13 @@ class Puppet::Parser::CodeMerger # Concatenates the logic in the array of parse results into one parse result # @return Puppet::Parser::AST::BlockExpression # def concatenate(parse_results) children = parse_results.select {|x| !x.nil? && x.code}.reduce([]) do |memo, parsed_class| memo + parsed_class.code.children end Puppet::Parser::AST::BlockExpression.new(:children => children) end -end \ No newline at end of file +end diff --git a/lib/puppet/pops/parser/code_merger.rb b/lib/puppet/pops/parser/code_merger.rb index 8634b329d..79e1328cd 100644 --- a/lib/puppet/pops/parser/code_merger.rb +++ b/lib/puppet/pops/parser/code_merger.rb @@ -1,17 +1,17 @@ class Puppet::Pops::Parser::CodeMerger # Concatenates the logic in the array of parse results into one parse result. # @return Puppet::Parser::AST::BlockExpression # def concatenate(parse_results) # this is a bit brute force as the result is already 3x ast with wrapped 4x content # this could be combined in a more elegant way, but it is only used to process a handful of files # at the beginning of a puppet run. TODO: Revisit for Puppet 4x when there is no 3x ast at the top. # children = parse_results.select {|x| !x.nil? && x.code}.reduce([]) do |memo, parsed_class| memo << parsed_class.code end - main = Puppet::Parser::AST::BlockExpression.new(:children => children) + Puppet::Parser::AST::BlockExpression.new(:children => children) end -end \ No newline at end of file +end diff --git a/spec/fixtures/integration/node/environment/sitedir/01_b.pp b/spec/fixtures/integration/node/environment/sitedir/01_b.pp index 4dce5eb83..25e339b4c 100644 --- a/spec/fixtures/integration/node/environment/sitedir/01_b.pp +++ b/spec/fixtures/integration/node/environment/sitedir/01_b.pp @@ -1,2 +1,6 @@ class b {} -$b = $a # error if $a not set in strict mode \ No newline at end of file + +# if the files are evaluated in the wrong order, the file 'b' has a reference +# to $a (set in file 'a') and with strict variable lookup should raise an error +# and fail this test. +$b = $a # error if $a not set in strict mode diff --git a/spec/fixtures/integration/node/environment/sitedir/04_include.pp b/spec/fixtures/integration/node/environment/sitedir/04_include.pp new file mode 100644 index 000000000..293067a7f --- /dev/null +++ b/spec/fixtures/integration/node/environment/sitedir/04_include.pp @@ -0,0 +1,2 @@ +include a, b +notify { "variables": message => "a: $a, b: $b" } diff --git a/spec/integration/node/environment_spec.rb b/spec/integration/node/environment_spec.rb index 105c70944..b99337ed5 100755 --- a/spec/integration/node/environment_spec.rb +++ b/spec/integration/node/environment_spec.rb @@ -1,106 +1,101 @@ #! /usr/bin/env ruby require 'spec_helper' require 'puppet_spec/files' require 'puppet_spec/scope' +require 'matchers/resource' describe Puppet::Node::Environment do include PuppetSpec::Files + include Matchers::Resource def a_module_in(name, dir) Dir.mkdir(dir) moddir = File.join(dir, name) Dir.mkdir(moddir) moddir end it "should be able to return each module from its environment with the environment, name, and path set correctly" do base = tmpfile("env_modules") Dir.mkdir(base) dirs = [] mods = {} %w{1 2}.each do |num| dir = File.join(base, "dir#{num}") dirs << dir mods["mod#{num}"] = a_module_in("mod#{num}", dir) end environment = Puppet::Node::Environment.create(:foo, dirs, '') environment.modules.each do |mod| mod.environment.should == environment mod.path.should == mods[mod.name] end end it "should not yield the same module from different module paths" do base = tmpfile("env_modules") Dir.mkdir(base) dirs = [] %w{1 2}.each do |num| dir = File.join(base, "dir#{num}") dirs << dir a_module_in("mod", dir) end environment = Puppet::Node::Environment.create(:foo, dirs, '') mods = environment.modules mods.length.should == 1 mods[0].path.should == File.join(base, "dir1", "mod") end shared_examples_for "the environment's initial import" do - include PuppetSpec::Scope - - let(:node) { Puppet::Node.new('testnode') } - it "a manifest referring to a directory invokes parsing of all its files in sorted order" do - # fixture has three files 00_a.pp, 01_b.pp, and 02_c.pp. The 'b' file depends on 'a' - # being evaluated first. The 'c' file is empty (to ensure empty things do not break the directory import). - # if the files are evaluated in the wrong order, the file 'b' has a reference to $a (set in file 'a') - # and with strict variable lookup should raise an error and fail this test. + # fixture has three files 00_a.pp, 01_b.pp, and 02_c.pp. The 'b' file + # depends on 'a' being evaluated first. The 'c' file is empty (to ensure + # empty things do not break the directory import). # dirname = my_fixture('sitedir') - # Set the manifest to the directory to make it parse and combine them when compiling - Puppet[:manifest] = dirname - # include the classes that are in the fixture files - node.stubs(:classes).returns(['a', 'b']) + # Set the manifest to the directory to make it parse and combine them when compiling + node = Puppet::Node.new('testnode', + :environment => Puppet::Node::Environment.create(:testing, [], dirname)) - # compile, to make the initial_import in the environment take place the correct way catalog = Puppet::Parser::Compiler.compile(node) - class_a = catalog.resource('Class[a]') - class_b = catalog.resource('Class[b]') - expect(class_a).to_not be_nil - expect(class_b).to_not be_nil + + expect(catalog).to have_resource('Class[a]') + expect(catalog).to have_resource('Class[b]') + expect(catalog).to have_resource('Notify[variables]').with_parameter(:message, "a: 10, b: 10") end end describe 'using classic parser' do before :each do Puppet[:parser] = 'current' # fixture uses variables that are set in a particular order (this ensures that files are parsed # and combined in the right order or an error will be raised if 'b' is evaluated before 'a'). Puppet[:strict_variables] = true end it_behaves_like "the environment's initial import" do end end describe 'using future parser' do before :each do Puppet[:parser] = 'future' # Turned off because currently future parser turns on the binder which causes lookup of facts # that are uninitialized and it will fail with errors for 'osfamily' etc. # This can be turned back on when the binder is taken out of the equation. # Puppet[:strict_variables] = true end it_behaves_like "the environment's initial import" do end -end + end end