diff options
| author | luke <luke@980ebf18-57e1-0310-9a29-db15c13687c0> | 2007-08-03 23:49:53 +0000 |
|---|---|---|
| committer | luke <luke@980ebf18-57e1-0310-9a29-db15c13687c0> | 2007-08-03 23:49:53 +0000 |
| commit | 97cd057177f18a0e6694aab0e440f86e0bf08d42 (patch) | |
| tree | 31a9808aa723180eb43f4fc2c952c3824f89d5d7 | |
| parent | 72f2ac39e036d3fea88569d17f3e455670bba063 (diff) | |
| download | puppet-97cd057177f18a0e6694aab0e440f86e0bf08d42.tar.gz puppet-97cd057177f18a0e6694aab0e440f86e0bf08d42.tar.xz puppet-97cd057177f18a0e6694aab0e440f86e0bf08d42.zip | |
Fixing #314 and #729; here's the changelog:
Refactored how the parser and interpreter relate,
so parsing is now effectively an atomic process (thus
fixing #314 and #729). This makes the interpreter less
prone to error and less prone to show the error to the
clients. Note that this means that if a configuration
fails to parse, then the previous, parseable configuration
will be used instead, so the client will not know that
the configuration failed to parse.
git-svn-id: https://reductivelabs.com/svn/puppet/trunk@2742 980ebf18-57e1-0310-9a29-db15c13687c0
| -rw-r--r-- | CHANGELOG | 10 | ||||
| -rw-r--r-- | lib/puppet/parser/ast.rb | 2 | ||||
| -rw-r--r-- | lib/puppet/parser/ast/component.rb | 4 | ||||
| -rw-r--r-- | lib/puppet/parser/ast/node.rb | 2 | ||||
| -rw-r--r-- | lib/puppet/parser/grammar.ra | 233 | ||||
| -rw-r--r-- | lib/puppet/parser/interpreter.rb | 251 | ||||
| -rw-r--r-- | lib/puppet/parser/parser.rb | 237 | ||||
| -rw-r--r-- | lib/puppet/parser/resource/param.rb | 2 | ||||
| -rwxr-xr-x | test/language/interpreter.rb | 404 | ||||
| -rwxr-xr-x | test/language/node.rb | 16 | ||||
| -rwxr-xr-x | test/language/parser.rb | 487 | ||||
| -rw-r--r-- | test/lib/puppettest/parsertesting.rb | 2 | ||||
| -rw-r--r-- | test/lib/puppettest/support/collection.rb | 2 |
13 files changed, 540 insertions, 1112 deletions
@@ -1,3 +1,12 @@ + Refactored how the parser and interpreter relate, + so parsing is now effectively an atomic process (thus + fixing #314 and #729). This makes the interpreter less + prone to error and less prone to show the error to the + clients. Note that this means that if a configuration + fails to parse, then the previous, parseable configuration + will be used instead, so the client will not know that + the configuration failed to parse. + Added support for managing interfaces, thanks to work by Paul Rose. @@ -9,6 +18,7 @@ of mandatory, thus allowing Mongrel to function as the CA. This is thanks to work done by Marcin Owsiany. +0.23.1 You can now specify relationships to classes, which work exactly like relationships to defined types: require => Class[myclass] diff --git a/lib/puppet/parser/ast.rb b/lib/puppet/parser/ast.rb index c6067d353..ce5e8263e 100644 --- a/lib/puppet/parser/ast.rb +++ b/lib/puppet/parser/ast.rb @@ -86,7 +86,7 @@ class Puppet::Parser::AST error = Puppet::Error.new(detail.to_s) # We can't use self.fail here because it always expects strings, # not exceptions. - raise adderrorcontext(error) + raise adderrorcontext(error, detail) end end diff --git a/lib/puppet/parser/ast/component.rb b/lib/puppet/parser/ast/component.rb index 43ca85e4a..65f310212 100644 --- a/lib/puppet/parser/ast/component.rb +++ b/lib/puppet/parser/ast/component.rb @@ -16,7 +16,7 @@ class Puppet::Parser::AST @name = :definition attr_accessor :classname, :arguments, :code, :scope, :keyword - attr_accessor :exported, :namespace, :interp, :virtual + attr_accessor :exported, :namespace, :parser, :virtual # These are retrieved when looking up the superclass attr_accessor :name @@ -140,7 +140,7 @@ class Puppet::Parser::AST end def find_parentclass - @interp.findclass(namespace, parentclass) + @parser.findclass(namespace, parentclass) end # Set our parent class, with a little check to avoid some potential diff --git a/lib/puppet/parser/ast/node.rb b/lib/puppet/parser/ast/node.rb index e873cac46..b9052168a 100644 --- a/lib/puppet/parser/ast/node.rb +++ b/lib/puppet/parser/ast/node.rb @@ -56,7 +56,7 @@ class Puppet::Parser::AST private # Search for the object matching our parent class. def find_parentclass - @interp.nodesearch(parentclass) + @parser.findnode(parentclass) end end end diff --git a/lib/puppet/parser/grammar.ra b/lib/puppet/parser/grammar.ra index e55bbf23e..c7216186a 100644 --- a/lib/puppet/parser/grammar.ra +++ b/lib/puppet/parser/grammar.ra @@ -498,13 +498,13 @@ import: IMPORT qtexts { # Disable definition inheritance for now. 8/27/06, luke #definition: DEFINE NAME argumentlist parent LBRACE statements RBRACE { definition: DEFINE classname argumentlist LBRACE statements RBRACE { - interp.newdefine classname(val[1]), :arguments => val[2], :code => val[4] + newdefine classname(val[1]), :arguments => val[2], :code => val[4] @lexer.indefine = false result = nil #} | DEFINE NAME argumentlist parent LBRACE RBRACE { } | DEFINE classname argumentlist LBRACE RBRACE { - interp.newdefine classname(val[1]), :arguments => val[2] + newdefine classname(val[1]), :arguments => val[2] @lexer.indefine = false result = nil } @@ -513,20 +513,20 @@ definition: DEFINE classname argumentlist LBRACE statements RBRACE { hostclass: CLASS classname classparent LBRACE statements RBRACE { # Our class gets defined in the parent namespace, not our own. @lexer.namepop - interp.newclass classname(val[1]), :code => val[4], :parent => val[2] + newclass classname(val[1]), :code => val[4], :parent => val[2] result = nil } | CLASS classname classparent LBRACE RBRACE { # Our class gets defined in the parent namespace, not our own. @lexer.namepop - interp.newclass classname(val[1]), :parent => val[2] + newclass classname(val[1]), :parent => val[2] result = nil } nodedef: NODE hostnames nodeparent LBRACE statements RBRACE { - interp.newnode val[1], :parent => val[2], :code => val[4] + newnode val[1], :parent => val[2], :code => val[4] result = nil } | NODE hostnames nodeparent LBRACE RBRACE { - interp.newnode val[1], :parent => val[2] + newnode val[1], :parent => val[2] result = nil } @@ -637,226 +637,9 @@ Puppet[:typecheck] = true Puppet[:paramcheck] = true ---- inner ---- -require 'puppet/parser/functions' -attr_reader :file, :interp -attr_accessor :files - -# Add context to a message; useful for error messages and such. -def addcontext(message, obj = nil) - obj ||= @lexer - - message += " on line %s" % obj.line - if file = obj.file - message += " in file %s" % file - end - - return message -end - -# Create an AST array out of all of the args -def aryfy(*args) - if args[0].instance_of?(AST::ASTArray) - result = args.shift - args.each { |arg| - result.push arg - } - else - result = ast AST::ASTArray, :children => args - end - - return result -end - -# Create an AST object, and automatically add the file and line information if -# available. -def ast(klass, hash = nil) - hash ||= {} - unless hash.include?(:line) - hash[:line] = @lexer.line - end - - unless hash.include?(:file) - if file = @lexer.file - hash[:file] = file - end - end - - return klass.new(hash) -end - -# Raise a Parse error. -def error(message) - if brace = @lexer.expected - message += "; expected '%s'" - end - except = Puppet::ParseError.new(message) - except.line = @lexer.line - if @lexer.file - except.file = @lexer.file - end - - raise except -end - -def file=(file) - unless FileTest.exists?(file) - unless file =~ /\.pp$/ - file = file + ".pp" - end - unless FileTest.exists?(file) - raise Puppet::Error, "Could not find file %s" % file - end - end - if @files.detect { |f| f.file == file } - raise Puppet::AlreadyImportedError.new("Import loop detected") - else - @files << Puppet::Util::LoadedFile.new(file) - @lexer.file = file - end -end - -# Import our files. -def import(file) - if Puppet[:ignoreimport] - return AST::ASTArray.new(:children => []) - end - # use a path relative to the file doing the importing - if @lexer.file - dir = @lexer.file.sub(%r{[^/]+$},'').sub(/\/$/, '') - else - dir = "." - end - if dir == "" - dir = "." - end - result = ast AST::ASTArray - - # We can't interpolate at this point since we don't have any - # scopes set up. Warn the user if they use a variable reference - pat = file - if pat.index("$") - Puppet.warning( - "The import of #{pat} contains a variable reference;" + - " variables are not interpolated for imports " + - "in file #{@lexer.file} at line #{@lexer.line}" - ) - end - files = Puppet::Module::find_manifests(pat, dir) - if files.size == 0 - raise Puppet::ImportError.new("No file(s) found for import " + - "of '#{pat}'") - end - - files.collect { |file| - parser = Puppet::Parser::Parser.new(interp) - parser.files = self.files - Puppet.debug("importing '%s'" % file) - - unless file =~ /^#{File::SEPARATOR}/ - file = File.join(dir, file) - end - begin - parser.file = file - rescue Puppet::AlreadyImportedError - # This file has already been imported to just move on - next - end - - # This will normally add code to the 'main' class. - parser.parse - } -end - -def initialize(interpreter) - @interp = interpreter - initvars() -end - -# Initialize or reset all of our variables. -def initvars - @lexer = Puppet::Parser::Lexer.new() - @files = [] -end - -# The fully qualifed name, with the full namespace. -def classname(name) - [@lexer.namespace, name].join("::").sub(/^::/, '') -end - -def on_error(token,value,stack) - #on '%s' at '%s' in\n'%s'" % [token,value,stack] - #error = "line %s: parse error after '%s'" % - # [@lexer.line,@lexer.last] - error = "Syntax error at '%s'" % [value] - - if brace = @lexer.expected - error += "; expected '%s'" % brace - end - - except = Puppet::ParseError.new(error) - except.line = @lexer.line - if @lexer.file - except.file = @lexer.file - end - - raise except -end - -# how should I do error handling here? -def parse(string = nil) - if string - self.string = string - end - begin - main = yyparse(@lexer,:scan) - rescue Racc::ParseError => except - error = Puppet::ParseError.new(except) - error.line = @lexer.line - error.file = @lexer.file - error.set_backtrace except.backtrace - raise error - rescue Puppet::ParseError => except - except.line ||= @lexer.line - except.file ||= @lexer.file - raise except - rescue Puppet::Error => except - # and this is a framework error - except.line ||= @lexer.line - except.file ||= @lexer.file - raise except - rescue Puppet::DevError => except - except.line ||= @lexer.line - except.file ||= @lexer.file - raise except - rescue => except - error = Puppet::DevError.new(except.message) - error.line = @lexer.line - error.file = @lexer.file - error.set_backtrace except.backtrace - raise error - end - if main - # Store the results as the top-level class. - interp.newclass("", :code => main) - return main - end -ensure - @lexer.clear -end - -# See if any of the files have changed. -def reparse? - if file = @files.detect { |file| file.changed? } - return file.stamp - else - return false - end -end - -def string=(string) - @lexer.string = string -end +# It got too annoying having code in a file that needs to be compiled. +require 'puppet/parser/parser_support' # Make emacs happy # Local Variables: diff --git a/lib/puppet/parser/interpreter.rb b/lib/puppet/parser/interpreter.rb index b4936dd23..9da1928b3 100644 --- a/lib/puppet/parser/interpreter.rb +++ b/lib/puppet/parser/interpreter.rb @@ -30,6 +30,7 @@ class Puppet::Parser::Interpreter begin options[:scope].function_include(classes.find_all { |c| options[:scope].findclass(c) }) rescue => detail + puts detail.backtrace raise Puppet::ParseError, "Could not evaluate classes for %s: %s" % [name, detail] end end @@ -59,6 +60,7 @@ class Puppet::Parser::Interpreter class << self attr_writer :ldap end + # just shorten the constant path a bit, using what amounts to an alias AST = Puppet::Parser::AST @@ -105,10 +107,6 @@ class Puppet::Parser::Interpreter end end - def clear - initparsevars - end - # Iteratively evaluate all of the objects. This finds all of the objects # that represent definitions and evaluates the definitions appropriately. # It also adds defaults and overrides as appropriate. @@ -264,48 +262,13 @@ class Puppet::Parser::Interpreter check_resource_collections(scope) end - # Find a class definition, relative to the current namespace. + # Create proxy methods, so the scopes can call the interpreter, since + # they don't have access to the parser. def findclass(namespace, name) - #find_or_load namespace, name, @classtable - fqfind namespace, name, @classtable + @parser.findclass(namespace, name) end - - # Find a component definition, relative to the current namespace. def finddefine(namespace, name) - #find_or_load namespace, name, @definetable - fqfind namespace, name, @definetable - end - - # The recursive method used to actually look these objects up. - def fqfind(namespace, name, table) - namespace = namespace.downcase - name = name.downcase - if name =~ /^::/ or namespace == "" - classname = name.sub(/^::/, '') - unless table[classname] - self.load(classname) - end - return table[classname] - end - ary = namespace.split("::") - - while ary.length > 0 - newname = (ary + [name]).join("::").sub(/^::/, '') - if obj = table[newname] or (self.load(newname) and obj = table[newname]) - return obj - end - - # Delete the second to last object, which reduces our namespace by one. - ary.pop - end - - # If we've gotten to this point without finding it, see if the name - # exists at the top namespace - if obj = table[name] or (self.load(name) and obj = table[name]) - return obj - end - - return nil + @parser.finddefine(namespace, name) end # create our interpreter @@ -335,8 +298,6 @@ class Puppet::Parser::Interpreter @setup = false - initparsevars() - # Set it to either the value or nil. This is currently only used # by the cfengine module. @classes = hash[:Classes] || [] @@ -361,55 +322,11 @@ class Puppet::Parser::Interpreter end @files = [] - @loaded = [] # Create our parser object parsefiles end - # Initialize or reset the variables related to parsing. - def initparsevars - @classtable = {} - @namespace = "main" - - @nodetable = {} - - @definetable = {} - end - - # Try to load a class, since we could not find it. - def load(classname) - return false if classname == "" - filename = classname.gsub("::", File::SEPARATOR) - - loaded = false - # First try to load the top-level module - mod = filename.scan(/^[\w-]+/).shift - unless @loaded.include?(mod) - @loaded << mod - begin - @parser.import(mod) - Puppet.info "Autoloaded module %s" % mod - loaded = true - rescue Puppet::ImportError => detail - # We couldn't load the module - end - end - - unless filename == mod and ! @loaded.include?(mod) - @loaded << mod - # Then the individual file - begin - @parser.import(filename) - Puppet.info "Autoloaded file %s from module %s" % [filename, mod] - loaded = true - rescue Puppet::ImportError => detail - # We couldn't load the file - end - end - return loaded - end - # Find the ldap node, return the class list and parent node specially, # and everything else in a parameter hash. def ldapsearch(node) @@ -505,115 +422,10 @@ class Puppet::Parser::Interpreter end end - # Split an fq name into a namespace and name - def namesplit(fullname) - ary = fullname.split("::") - n = ary.pop || "" - ns = ary.join("::") - return ns, n - end - - # Create a new class, or merge with an existing class. - def newclass(name, options = {}) - name = name.downcase - if @definetable.include?(name) - raise Puppet::ParseError, "Cannot redefine class %s as a definition" % - name - end - code = options[:code] - parent = options[:parent] - - # If the class is already defined, then add code to it. - if other = @classtable[name] - # Make sure the parents match - if parent and other.parentclass and (parent != other.parentclass) - @parser.error("Class %s is already defined at %s:%s; cannot redefine" % [name, other.file, other.line]) - end - - # This might be dangerous... - if parent and ! other.parentclass - other.parentclass = parent - end - - # This might just be an empty, stub class. - if code - tmp = name - if tmp == "" - tmp = "main" - end - - Puppet.debug @parser.addcontext("Adding code to %s" % tmp) - # Else, add our code to it. - if other.code and code - other.code.children += code.children - else - other.code ||= code - end - end - else - # Define it anew. - # Note we're doing something somewhat weird here -- we're setting - # the class's namespace to its fully qualified name. This means - # anything inside that class starts looking in that namespace first. - args = {:namespace => name, :classname => name, :interp => self} - args[:code] = code if code - args[:parentclass] = parent if parent - @classtable[name] = @parser.ast AST::HostClass, args - end - - return @classtable[name] - end - - # Create a new definition. - def newdefine(name, options = {}) - name = name.downcase - if @classtable.include?(name) - raise Puppet::ParseError, "Cannot redefine class %s as a definition" % - name - end - # Make sure our definition doesn't already exist - if other = @definetable[name] - @parser.error("%s is already defined at %s:%s; cannot redefine" % [name, other.file, other.line]) - end - - ns, whatever = namesplit(name) - args = { - :namespace => ns, - :arguments => options[:arguments], - :code => options[:code], - :classname => name - } - - [:code, :arguments].each do |param| - args[param] = options[param] if options[param] - end - - @definetable[name] = @parser.ast AST::Component, args - end - - # Create a new node. Nodes are special, because they're stored in a global - # table, not according to namespaces. - def newnode(names, options = {}) - names = [names] unless names.instance_of?(Array) - names.collect do |name| - name = name.to_s.downcase - if other = @nodetable[name] - @parser.error("Node %s is already defined at %s:%s; cannot redefine" % [other.name, other.file, other.line]) - end - name = name.to_s if name.is_a?(Symbol) - args = { - :name => name, - } - if options[:code] - args[:code] = options[:code] - end - if options[:parent] - args[:parentclass] = options[:parent] - end - @nodetable[name] = @parser.ast(AST::Node, args) - @nodetable[name].classname = name - @nodetable[name].interp = self - @nodetable[name] + # Pass these methods through to the parser. + [:newclass, :newdefine, :newnode].each do |name| + define_method(name) do |*args| + @parser.send(name, *args) end end @@ -662,7 +474,7 @@ class Puppet::Parser::Interpreter # See if our node was defined in the code. def nodesearch_code(name) - @nodetable[name] + @parser.nodes[name] end # Look for external node definitions. @@ -815,29 +627,40 @@ class Puppet::Parser::Interpreter end end - # Reset our parse tables. - clear() - - # Create a new parser, just to keep things fresh. - @parser = Puppet::Parser::Parser.new(self) + # Create a new parser, just to keep things fresh. Don't replace our + # current parser until we know weverything works. + newparser = Puppet::Parser::Parser.new() if @code - @parser.string = @code + newparser.string = @code else - @parser.file = @file - # Mark when we parsed, so we can check freshness - @parsedate = File.stat(@file).ctime.to_i + newparser.file = @file end # Parsing stores all classes and defines and such in their # various tables, so we don't worry about the return. - if @local - @parser.parse - else - benchmark(:info, "Parsed manifest") do - @parser.parse + begin + if @local + newparser.parse + else + benchmark(:info, "Parsed manifest") do + newparser.parse + end + end + # We've gotten this far, so it's ok to swap the parsers. + oldparser = @parser + @parser = newparser + if oldparser + oldparser.clear + end + + # Mark when we parsed, so we can check freshness + @parsedate = Time.now.to_i + rescue => detail + if Puppet[:trace] + puts detail.backtrace end + Puppet.err "Could not parse; using old configuration: %s" % detail end - @parsedate = Time.now.to_i end # Store the configs into the database. diff --git a/lib/puppet/parser/parser.rb b/lib/puppet/parser/parser.rb index f0bbeb499..21a054223 100644 --- a/lib/puppet/parser/parser.rb +++ b/lib/puppet/parser/parser.rb @@ -29,227 +29,10 @@ module Puppet class Parser < Racc::Parser -module_eval <<'..end grammar.ra modeval..id72fb6a61b8', 'grammar.ra', 640 -require 'puppet/parser/functions' +module_eval <<'..end grammar.ra modeval..idc5e5087e93', 'grammar.ra', 640 -attr_reader :file, :interp -attr_accessor :files - -# Add context to a message; useful for error messages and such. -def addcontext(message, obj = nil) - obj ||= @lexer - - message += " on line %s" % obj.line - if file = obj.file - message += " in file %s" % file - end - - return message -end - -# Create an AST array out of all of the args -def aryfy(*args) - if args[0].instance_of?(AST::ASTArray) - result = args.shift - args.each { |arg| - result.push arg - } - else - result = ast AST::ASTArray, :children => args - end - - return result -end - -# Create an AST object, and automatically add the file and line information if -# available. -def ast(klass, hash = nil) - hash ||= {} - unless hash.include?(:line) - hash[:line] = @lexer.line - end - - unless hash.include?(:file) - if file = @lexer.file - hash[:file] = file - end - end - - return klass.new(hash) -end - -# Raise a Parse error. -def error(message) - if brace = @lexer.expected - message += "; expected '%s'" - end - except = Puppet::ParseError.new(message) - except.line = @lexer.line - if @lexer.file - except.file = @lexer.file - end - - raise except -end - -def file=(file) - unless FileTest.exists?(file) - unless file =~ /\.pp$/ - file = file + ".pp" - end - unless FileTest.exists?(file) - raise Puppet::Error, "Could not find file %s" % file - end - end - if @files.detect { |f| f.file == file } - raise Puppet::AlreadyImportedError.new("Import loop detected") - else - @files << Puppet::Util::LoadedFile.new(file) - @lexer.file = file - end -end - -# Import our files. -def import(file) - if Puppet[:ignoreimport] - return AST::ASTArray.new(:children => []) - end - # use a path relative to the file doing the importing - if @lexer.file - dir = @lexer.file.sub(%r{[^/]+$},'').sub(/\/$/, '') - else - dir = "." - end - if dir == "" - dir = "." - end - result = ast AST::ASTArray - - # We can't interpolate at this point since we don't have any - # scopes set up. Warn the user if they use a variable reference - pat = file - if pat.index("$") - Puppet.warning( - "The import of #{pat} contains a variable reference;" + - " variables are not interpolated for imports " + - "in file #{@lexer.file} at line #{@lexer.line}" - ) - end - files = Puppet::Module::find_manifests(pat, dir) - if files.size == 0 - raise Puppet::ImportError.new("No file(s) found for import " + - "of '#{pat}'") - end - - files.collect { |file| - parser = Puppet::Parser::Parser.new(interp) - parser.files = self.files - Puppet.debug("importing '%s'" % file) - - unless file =~ /^#{File::SEPARATOR}/ - file = File.join(dir, file) - end - begin - parser.file = file - rescue Puppet::AlreadyImportedError - # This file has already been imported to just move on - next - end - - # This will normally add code to the 'main' class. - parser.parse - } -end - -def initialize(interpreter) - @interp = interpreter - initvars() -end - -# Initialize or reset all of our variables. -def initvars - @lexer = Puppet::Parser::Lexer.new() - @files = [] -end - -# The fully qualifed name, with the full namespace. -def classname(name) - [@lexer.namespace, name].join("::").sub(/^::/, '') -end - -def on_error(token,value,stack) - #on '%s' at '%s' in\n'%s'" % [token,value,stack] - #error = "line %s: parse error after '%s'" % - # [@lexer.line,@lexer.last] - error = "Syntax error at '%s'" % [value] - - if brace = @lexer.expected - error += "; expected '%s'" % brace - end - - except = Puppet::ParseError.new(error) - except.line = @lexer.line - if @lexer.file - except.file = @lexer.file - end - - raise except -end - -# how should I do error handling here? -def parse(string = nil) - if string - self.string = string - end - begin - main = yyparse(@lexer,:scan) - rescue Racc::ParseError => except - error = Puppet::ParseError.new(except) - error.line = @lexer.line - error.file = @lexer.file - error.set_backtrace except.backtrace - raise error - rescue Puppet::ParseError => except - except.line ||= @lexer.line - except.file ||= @lexer.file - raise except - rescue Puppet::Error => except - # and this is a framework error - except.line ||= @lexer.line - except.file ||= @lexer.file - raise except - rescue Puppet::DevError => except - except.line ||= @lexer.line - except.file ||= @lexer.file - raise except - rescue => except - error = Puppet::DevError.new(except.message) - error.line = @lexer.line - error.file = @lexer.file - error.set_backtrace except.backtrace - raise error - end - if main - # Store the results as the top-level class. - interp.newclass("", :code => main) - return main - end -ensure - @lexer.clear -end - -# See if any of the files have changed. -def reparse? - if file = @files.detect { |file| file.changed? } - return file.stamp - else - return false - end -end - -def string=(string) - @lexer.string = string -end +# It got too annoying having code in a file that needs to be compiled. +require 'puppet/parser/parser_support' # Make emacs happy # Local Variables: @@ -258,7 +41,7 @@ end # $Id$ -..end grammar.ra modeval..id72fb6a61b8 +..end grammar.ra modeval..idc5e5087e93 ##### racc 1.4.5 generates ### @@ -1741,7 +1524,7 @@ module_eval <<'.,.,', 'grammar.ra', 496 module_eval <<'.,.,', 'grammar.ra', 506 def _reduce_135( val, _values, result ) - interp.newdefine classname(val[1]), :arguments => val[2], :code => val[4] + newdefine classname(val[1]), :arguments => val[2], :code => val[4] @lexer.indefine = false result = nil @@ -1752,7 +1535,7 @@ module_eval <<'.,.,', 'grammar.ra', 506 module_eval <<'.,.,', 'grammar.ra', 510 def _reduce_136( val, _values, result ) - interp.newdefine classname(val[1]), :arguments => val[2] + newdefine classname(val[1]), :arguments => val[2] @lexer.indefine = false result = nil result @@ -1763,7 +1546,7 @@ module_eval <<'.,.,', 'grammar.ra', 518 def _reduce_137( val, _values, result ) # Our class gets defined in the parent namespace, not our own. @lexer.namepop - interp.newclass classname(val[1]), :code => val[4], :parent => val[2] + newclass classname(val[1]), :code => val[4], :parent => val[2] result = nil result end @@ -1773,7 +1556,7 @@ module_eval <<'.,.,', 'grammar.ra', 523 def _reduce_138( val, _values, result ) # Our class gets defined in the parent namespace, not our own. @lexer.namepop - interp.newclass classname(val[1]), :parent => val[2] + newclass classname(val[1]), :parent => val[2] result = nil result end @@ -1781,7 +1564,7 @@ module_eval <<'.,.,', 'grammar.ra', 523 module_eval <<'.,.,', 'grammar.ra', 528 def _reduce_139( val, _values, result ) - interp.newnode val[1], :parent => val[2], :code => val[4] + newnode val[1], :parent => val[2], :code => val[4] result = nil result end @@ -1789,7 +1572,7 @@ module_eval <<'.,.,', 'grammar.ra', 528 module_eval <<'.,.,', 'grammar.ra', 531 def _reduce_140( val, _values, result ) - interp.newnode val[1], :parent => val[2] + newnode val[1], :parent => val[2] result = nil result end diff --git a/lib/puppet/parser/resource/param.rb b/lib/puppet/parser/resource/param.rb index fc3d72c85..4332bee85 100644 --- a/lib/puppet/parser/resource/param.rb +++ b/lib/puppet/parser/resource/param.rb @@ -12,7 +12,7 @@ class Puppet::Parser::Resource::Param end def inspect - "#<#{self.class} @name => #{self.name}, @value => #{self.value}, @source => #{self.source.type}>" + "#<#{self.class} @name => #{self.name}, @value => #{self.value}, @source => #{self.source.name}>" end def line_to_i diff --git a/test/language/interpreter.rb b/test/language/interpreter.rb index 944afa9fa..6352a147e 100755 --- a/test/language/interpreter.rb +++ b/test/language/interpreter.rb @@ -160,205 +160,6 @@ class TestInterpreter < PuppetTest::TestCase newdate = interp.parsedate assert(date != newdate, "Parsedate was not updated") end - - # Make sure our node gets added to the node table. - def test_newnode - interp = mkinterp - - # First just try calling it directly - assert_nothing_raised { - interp.newnode("mynode", :code => :yay) - } - - assert_equal(:yay, interp.nodesearch_code("mynode").code) - - # Now make sure that trying to redefine it throws an error. - assert_raise(Puppet::ParseError) { - interp.newnode("mynode", {}) - } - - # Now try one with no code - assert_nothing_raised { - interp.newnode("simplenode", :parent => :foo) - } - - # Make sure trying to get the parentclass throws an error - assert_raise(Puppet::ParseError) do - interp.nodesearch_code("simplenode").parentobj - end - - # Now define the parent node - interp.newnode(:foo) - - # And make sure we get things back correctly - assert_equal("foo", interp.nodesearch_code("simplenode").parentobj.classname) - assert_nil(interp.nodesearch_code("simplenode").code) - - # Now make sure that trying to redefine it throws an error. - assert_raise(Puppet::ParseError) { - interp.newnode("mynode", {}) - } - - # Test multiple names - names = ["one", "two", "three"] - assert_nothing_raised { - interp.newnode(names, {:code => :yay, :parent => :foo}) - } - - names.each do |name| - assert_equal(:yay, interp.nodesearch_code(name).code) - assert_equal("foo", interp.nodesearch_code(name).parentobj.name) - # Now make sure that trying to redefine it throws an error. - assert_raise(Puppet::ParseError) { - interp.newnode(name, {}) - } - end - end - - def test_fqfind - interp = mkinterp - - table = {} - # Define a bunch of things. - %w{a c a::b a::b::c a::c a::b::c::d a::b::c::d::e::f c::d}.each do |string| - table[string] = string - end - - check = proc do |namespace, hash| - hash.each do |thing, result| - assert_equal(result, interp.fqfind(namespace, thing, table), - "Could not find %s in %s" % [thing, namespace]) - end - end - - # Now let's do some test lookups. - - # First do something really simple - check.call "a", "b" => "a::b", "b::c" => "a::b::c", "d" => nil, "::c" => "c" - - check.call "a::b", "c" => "a::b::c", "b" => "a::b", "a" => "a" - - check.call "a::b::c::d::e", "c" => "a::b::c", "::c" => "c", - "c::d" => "a::b::c::d", "::c::d" => "c::d" - - check.call "", "a" => "a", "a::c" => "a::c" - end - - def test_newdefine - interp = mkinterp - - assert_nothing_raised { - interp.newdefine("mydefine", :code => :yay, - :arguments => ["a", stringobj("b")]) - } - - mydefine = interp.finddefine("", "mydefine") - assert(mydefine, "Could not find definition") - assert_equal("mydefine", interp.finddefine("", "mydefine").classname) - assert_equal("", mydefine.namespace) - assert_equal("mydefine", mydefine.classname) - - assert_raise(Puppet::ParseError) do - interp.newdefine("mydefine", :code => :yay, - :arguments => ["a", stringobj("b")]) - end - - # Now define the same thing in a different scope - assert_nothing_raised { - interp.newdefine("other::mydefine", :code => :other, - :arguments => ["a", stringobj("b")]) - } - other = interp.finddefine("other", "mydefine") - assert(other, "Could not find definition") - assert(interp.finddefine("", "other::mydefine"), - "Could not find other::mydefine") - assert_equal(:other, other.code) - assert_equal("other", other.namespace) - assert_equal("other::mydefine", other.classname) - end - - def test_newclass - interp = mkinterp - - mkcode = proc do |ary| - classes = ary.collect do |string| - AST::FlatString.new(:value => string) - end - AST::ASTArray.new(:children => classes) - end - scope = Puppet::Parser::Scope.new(:interp => interp) - - # First make sure that code is being appended - code = mkcode.call(%w{original code}) - - klass = nil - assert_nothing_raised { - klass = interp.newclass("myclass", :code => code) - } - - assert(klass, "Did not return class") - - assert(interp.findclass("", "myclass"), "Could not find definition") - assert_equal("myclass", interp.findclass("", "myclass").classname) - assert_equal(%w{original code}, - interp.findclass("", "myclass").code.evaluate(:scope => scope)) - - # Now create the same class name in a different scope - assert_nothing_raised { - klass = interp.newclass("other::myclass", - :code => mkcode.call(%w{something diff})) - } - assert(klass, "Did not return class") - other = interp.findclass("other", "myclass") - assert(other, "Could not find class") - assert(interp.findclass("", "other::myclass"), "Could not find class") - assert_equal("other::myclass", other.classname) - assert_equal("other::myclass", other.namespace) - assert_equal(%w{something diff}, - interp.findclass("other", "myclass").code.evaluate(:scope => scope)) - - # Newclass behaves differently than the others -- it just appends - # the code to the existing class. - code = mkcode.call(%w{something new}) - assert_nothing_raised do - klass = interp.newclass("myclass", :code => code) - end - assert(klass, "Did not return class when appending") - assert_equal(%w{original code something new}, - interp.findclass("", "myclass").code.evaluate(:scope => scope)) - - # Make sure newclass deals correctly with nodes with no code - klass = interp.newclass("nocode") - assert(klass, "Did not return class") - - assert_nothing_raised do - klass = interp.newclass("nocode", :code => mkcode.call(%w{yay test})) - end - assert(klass, "Did not return class with no code") - assert_equal(%w{yay test}, - interp.findclass("", "nocode").code.evaluate(:scope => scope)) - - # Then try merging something into nothing - interp.newclass("nocode2", :code => mkcode.call(%w{foo test})) - assert(klass, "Did not return class with no code") - - assert_nothing_raised do - klass = interp.newclass("nocode2") - end - assert(klass, "Did not return class with no code") - assert_equal(%w{foo test}, - interp.findclass("", "nocode2").code.evaluate(:scope => scope)) - - # And lastly, nothing and nothing - klass = interp.newclass("nocode3") - assert(klass, "Did not return class with no code") - - assert_nothing_raised do - klass = interp.newclass("nocode3") - end - assert(klass, "Did not return class with no code") - assert_nil(interp.findclass("", "nocode3").code) - end # Make sure class, node, and define methods are case-insensitive def test_structure_case_insensitivity @@ -388,88 +189,6 @@ class TestInterpreter < PuppetTest::TestCase assert_equal(result, interp.nodesearch("yaYtEst.domAin.cOm"), "yaYtEst.domAin.cOm was not matched") end - - # Now make sure we get appropriate behaviour with parent class conflicts. - def test_newclass_parentage - interp = mkinterp - interp.newclass("base1") - interp.newclass("one::two::three") - - # First create it with no parentclass. - assert_nothing_raised { - interp.newclass("sub") - } - assert(interp.findclass("", "sub"), "Could not find definition") - assert_nil(interp.findclass("", "sub").parentclass) - - # Make sure we can't set the parent class to ourself. - assert_raise(Puppet::ParseError) { - interp.newclass("sub", :parent => "sub") - } - - # Now create another one, with a parentclass. - assert_nothing_raised { - interp.newclass("sub", :parent => "base1") - } - - # Make sure we get the right parent class, and make sure it's not an object. - assert_equal("base1", - interp.findclass("", "sub").parentclass) - assert_equal(interp.findclass("", "base1"), - interp.findclass("", "sub").parentobj) - - # Now make sure we get a failure if we try to conflict. - assert_raise(Puppet::ParseError) { - interp.newclass("sub", :parent => "one::two::three") - } - - # Make sure that failure didn't screw us up in any way. - assert_equal(interp.findclass("", "base1"), - interp.findclass("", "sub").parentobj) - # But make sure we can create a class with a fq parent - assert_nothing_raised { - interp.newclass("another", :parent => "one::two::three") - } - assert_equal(interp.findclass("", "one::two::three"), - interp.findclass("", "another").parentobj) - - end - - def test_namesplit - interp = mkinterp - - assert_nothing_raised do - {"base::sub" => %w{base sub}, - "main" => ["", "main"], - "one::two::three::four" => ["one::two::three", "four"], - }.each do |name, ary| - result = interp.namesplit(name) - assert_equal(ary, result, "%s split to %s" % [name, result]) - end - end - end - - # Make sure you can't have classes and defines with the same name in the - # same scope. - def test_classes_beat_defines - interp = mkinterp - - assert_nothing_raised { - interp.newclass("yay::funtest") - } - - assert_raise(Puppet::ParseError) do - interp.newdefine("yay::funtest") - end - - assert_nothing_raised { - interp.newdefine("yay::yaytest") - } - - assert_raise(Puppet::ParseError) do - interp.newclass("yay::yaytest") - end - end # Make sure our whole chain works. def test_evaluate @@ -925,118 +644,29 @@ class TestInterpreter < PuppetTest::TestCase assert_equal({"base" => "true", "center" => "boo", "master" => "far"}, node.parameters, "node parameters were not set correctly with the top node") end - # Setup a module. - def mk_module(name, files = {}) - mdir = File.join(@dir, name) - mandir = File.join(mdir, "manifests") - FileUtils.mkdir_p mandir + # Make sure that reparsing is atomic -- failures don't cause a broken state, and we aren't subject + # to race conditions if someone contacts us while we're reparsing. + def test_atomic_reparsing + Puppet[:filetimeout] = -10 + file = tempfile + File.open(file, "w") { |f| f.puts %{file { '/tmp': ensure => directory }} } + interp = mkinterp :Manifest => file, :UseNodes => false - if defs = files[:define] - files.delete(:define) + assert_nothing_raised("Could not compile the first time") do + interp.run("yay", {}) end - Dir.chdir(mandir) do - files.each do |file, classes| - File.open("%s.pp" % file, "w") do |f| - classes.each { |klass| - if defs - f.puts "define %s {}" % klass - else - f.puts "class %s {}" % klass - end - } - end - end - end - end - - # #596 - make sure classes and definitions load automatically if they're in modules, so we don't have to manually load each one. - def test_module_autoloading - @dir = tempfile - Puppet[:modulepath] = @dir - - FileUtils.mkdir_p @dir - - interp = mkinterp - - # Make sure we fail like normal for actually missing classes - assert_nil(interp.findclass("", "nosuchclass"), "Did not return nil on missing classes") - - # test the simple case -- the module class itself - name = "simple" - mk_module(name, :init => [name]) - # Try to load the module automatically now - klass = interp.findclass("", name) - assert_instance_of(AST::HostClass, klass, "Did not autoload class from module init file") - assert_equal(name, klass.classname, "Incorrect class was returned") + oldparser = interp.send(:instance_variable_get, "@parser") - # Try loading the simple module when we're in something other than the base namespace. - interp = mkinterp - klass = interp.findclass("something::else", name) - assert_instance_of(AST::HostClass, klass, "Did not autoload class from module init file") - assert_equal(name, klass.classname, "Incorrect class was returned") - - # Now try it with a definition as the base file - name = "simpdef" - mk_module(name, :define => true, :init => [name]) - - klass = interp.finddefine("", name) - assert_instance_of(AST::Component, klass, "Did not autoload class from module init file") - assert_equal(name, klass.classname, "Incorrect class was returned") - - # Now try it with namespace classes where both classes are in the init file - interp = mkinterp - modname = "both" - name = "sub" - mk_module(modname, :init => %w{both both::sub}) - - # First try it with a namespace - klass = interp.findclass("both", name) - assert_instance_of(AST::HostClass, klass, "Did not autoload sub class from module init file with a namespace") - assert_equal("both::sub", klass.classname, "Incorrect class was returned") - - # Now try it using the fully qualified name - interp = mkinterp - klass = interp.findclass("", "both::sub") - assert_instance_of(AST::HostClass, klass, "Did not autoload sub class from module init file with no namespace") - assert_equal("both::sub", klass.classname, "Incorrect class was returned") - - - # Now try it with the class in a different file - interp = mkinterp - modname = "separate" - name = "sub" - mk_module(modname, :init => %w{separate}, :sub => %w{separate::sub}) - - # First try it with a namespace - klass = interp.findclass("separate", name) - assert_instance_of(AST::HostClass, klass, "Did not autoload sub class from separate file with a namespace") - assert_equal("separate::sub", klass.classname, "Incorrect class was returned") - - # Now try it using the fully qualified name - interp = mkinterp - klass = interp.findclass("", "separate::sub") - assert_instance_of(AST::HostClass, klass, "Did not autoload sub class from separate file with no namespace") - assert_equal("separate::sub", klass.classname, "Incorrect class was returned") - - # Now make sure we don't get a failure when there's no module file - interp = mkinterp - modname = "alone" - name = "sub" - mk_module(modname, :sub => %w{alone::sub}) - - # First try it with a namespace - assert_nothing_raised("Could not autoload file when module file is missing") do - klass = interp.findclass("alone", name) + # Now add a syntax failure + File.open(file, "w") { |f| f.puts %{file { /tmp: ensure => directory }} } + assert_nothing_raised("Could not compile the first time") do + interp.run("yay", {}) end - assert_instance_of(AST::HostClass, klass, "Did not autoload sub class from alone file with a namespace") - assert_equal("alone::sub", klass.classname, "Incorrect class was returned") - # Now try it using the fully qualified name - interp = mkinterp - klass = interp.findclass("", "alone::sub") - assert_instance_of(AST::HostClass, klass, "Did not autoload sub class from alone file with no namespace") - assert_equal("alone::sub", klass.classname, "Incorrect class was returned") + # And make sure the old parser is still there + newparser = interp.send(:instance_variable_get, "@parser") + assert_equal(oldparser.object_id, newparser.object_id, "Failed parser still replaced existing parser") end end diff --git a/test/language/node.rb b/test/language/node.rb index 68459d141..61983df92 100755 --- a/test/language/node.rb +++ b/test/language/node.rb @@ -61,29 +61,29 @@ class TestParser < Test::Unit::TestCase end interp = nil code ||= "node #{hostnames.join(", ")} { }" - assert_nothing_raised { - interp = mkinterp :Code => code - } + parser = mkparser + parser.string = code # Strip quotes hostnames.map! { |s| s.sub(/^['"](.*)['"]$/, "\\1") } # parse - assert_nothing_raised { - interp.send(:parsefiles) + assert_nothing_raised("Could not parse '%s'" % code) { + parser.parse } # Now make sure we can look up each of the names hostnames.each do |name| - assert(interp.nodesearch(name), + assert(parser.findnode(name), "Could not find node %s" % name.inspect) end end def check_nonparseable(hostname) interp = nil + parser = mkparser + parser.string = "node #{hostname} { }" assert_raise(Puppet::DevError, Puppet::ParseError, "#{hostname} passed") { - interp = mkinterp :Code => "node #{hostname} { }" - interp.send(:parsefiles) + parser.parse } end diff --git a/test/language/parser.rb b/test/language/parser.rb index e4b913ab8..afffa9293 100755 --- a/test/language/parser.rb +++ b/test/language/parser.rb @@ -2,6 +2,7 @@ $:.unshift("../lib").unshift("../../lib") if __FILE__ =~ /\.rb$/ +require 'mocha' require 'puppet' require 'puppet/parser/parser' require 'puppettest' @@ -37,12 +38,13 @@ class TestParser < Test::Unit::TestCase def test_failers failers { |file| parser = mkparser + interp = mkinterp Puppet.debug("parsing failer %s" % file) if __FILE__ == $0 assert_raise(Puppet::ParseError) { parser.file = file ast = parser.parse - scope = mkscope :interp => parser.interp - ast.evaluate :scope => scope + scope = mkscope :interp => interp + ast.classes[""].evaluate :scope => scope } Puppet::Type.allclear } @@ -275,9 +277,7 @@ class TestParser < Test::Unit::TestCase ret = parser.parse } - assert_instance_of(AST::ASTArray, ret) - - ret.each do |obj| + ret.classes[""].code.each do |obj| assert_instance_of(AST::Collection, obj) end end @@ -368,12 +368,13 @@ file { "/tmp/yayness": str1 = %{if true { #{exec.call("true")} }} ret = nil assert_nothing_raised { - ret = parser.parse(str1)[0] + ret = parser.parse(str1).classes[""].code[0] } assert_instance_of(Puppet::Parser::AST::IfStatement, ret) + parser.clear str2 = %{if true { #{exec.call("true")} } else { #{exec.call("false")} }} assert_nothing_raised { - ret = parser.parse(str2)[0] + ret = parser.parse(str2).classes[""].code[0] } assert_instance_of(Puppet::Parser::AST::IfStatement, ret) assert_instance_of(Puppet::Parser::AST::Else, ret.else) @@ -381,13 +382,12 @@ file { "/tmp/yayness": def test_hostclass parser = mkparser - interp = parser.interp assert_nothing_raised { parser.parse %{class myclass { class other {} }} } - assert(interp.findclass("", "myclass"), "Could not find myclass") - assert(interp.findclass("", "myclass::other"), "Could not find myclass::other") + assert(parser.classes["myclass"], "Could not find myclass") + assert(parser.classes["myclass::other"], "Could not find myclass::other") assert_nothing_raised { parser.parse "class base {} @@ -395,17 +395,16 @@ file { "/tmp/yayness": class deep::sub inherits base {} }" } - sub = interp.findclass("", "container::deep::sub") + sub = parser.classes["container::deep::sub"] assert(sub, "Could not find sub") - assert_equal("base", sub.parentobj.classname) # Now try it with a parent class being a fq class assert_nothing_raised { parser.parse "class container::one inherits container::deep::sub {}" } - sub = interp.findclass("", "container::one") + sub = parser.classes["container::one"] assert(sub, "Could not find one") - assert_equal("container::deep::sub", sub.parentobj.classname) + assert_equal("container::deep::sub", sub.parentclass) # Finally, try including a qualified class assert_nothing_raised("Could not include fully qualified class") { @@ -415,25 +414,23 @@ file { "/tmp/yayness": def test_topnamespace parser = mkparser - parser.interp.clear # Make sure we put the top-level code into a class called "" in # the "" namespace assert_nothing_raised do out = parser.parse "" - assert_nil(out) - assert_nil(parser.interp.findclass("", "")) + assert_instance_of(Puppet::Parser::Parser::ASTSet, out) + assert_nil(parser.classes[""], "Got a 'main' class when we had no code") end # Now try something a touch more complicated - parser.interp.clear + parser.initvars assert_nothing_raised do out = parser.parse "Exec { path => '/usr/bin:/usr/sbin' }" - assert_instance_of(AST::ASTArray, out) - assert_equal("", parser.interp.findclass("", "").classname) - assert_equal("", parser.interp.findclass("", "").namespace) - assert_equal(out.object_id, parser.interp.findclass("", "").code.object_id) + assert_instance_of(Puppet::Parser::Parser::ASTSet, out) + assert_equal("", parser.classes[""].classname) + assert_equal("", parser.classes[""].namespace) end end @@ -476,18 +473,19 @@ file { "/tmp/yayness": ret = parser.parse("#{at}file { '/tmp/testing': owner => root }") end - assert_equal("/tmp/testing", ret[0].title.value) + assert_instance_of(AST::ASTArray, ret.classes[""].code) + resdef = ret.classes[""].code[0] + assert_instance_of(AST::ResourceDef, resdef) + assert_equal("/tmp/testing", resdef.title.value) # We always get an astarray back, so... - assert_instance_of(AST::ResourceDef, ret[0]) - check.call(ret[0], "simple resource") + check.call(resdef, "simple resource") # Now let's try it with multiple resources in the same spec assert_nothing_raised do ret = parser.parse("#{at}file { ['/tmp/1', '/tmp/2']: owner => root }") end - assert_instance_of(AST::ASTArray, ret) - ret.each do |res| + ret.classes[""].each do |res| assert_instance_of(AST::ResourceDef, res) check.call(res, "multiresource") end @@ -495,11 +493,11 @@ file { "/tmp/yayness": # Now evaluate these scope = mkscope - klass = scope.interp.newclass "" + klass = parser.newclass "" scope.source = klass assert_nothing_raised do - ret.evaluate :scope => scope + ret.classes[""].evaluate :scope => scope end # Make sure we can find both of them @@ -527,16 +525,14 @@ file { "/tmp/yayness": arrow = "<<||>>" end - check = proc do |coll| - assert_instance_of(AST::Collection, coll) - assert_equal(form, coll.form) - end - ret = nil assert_nothing_raised do ret = parser.parse("File #{arrow}") end - check.call(ret[0]) + + coll = ret.classes[""].code[0] + assert_instance_of(AST::Collection, coll) + assert_equal(form, coll.form) end end @@ -548,7 +544,7 @@ file { "/tmp/yayness": res = nil assert_nothing_raised do - res = parser.parse(str)[0] + res = parser.parse(str).classes[""].code[0] end assert_instance_of(AST::Collection, res) @@ -571,7 +567,7 @@ file { "/tmp/yayness": res = nil assert_nothing_raised do - res = parser.parse(str)[0] + res = parser.parse(str).classes[""].code[0] end assert_instance_of(AST::Collection, res) @@ -595,7 +591,7 @@ file { "/tmp/yayness": res = nil assert_nothing_raised("Could not parse '#{test}'") do - res = parser.parse(str)[0] + res = parser.parse(str).classes[""].code[0] end assert_instance_of(AST::Collection, res) @@ -634,13 +630,11 @@ file { "/tmp/yayness": def test_fully_qualified_definitions parser = mkparser - interp = parser.interp assert_nothing_raised("Could not parse fully-qualified definition") { parser.parse %{define one::two { }} } - assert(interp.finddefine("", "one::two"), "Could not find one::two with no namespace") - assert(interp.finddefine("one", "two"), "Could not find two in namespace one") + assert(parser.definitions["one::two"], "Could not find one::two with no namespace") # Now try using the definition assert_nothing_raised("Could not parse fully-qualified definition usage") { @@ -772,9 +766,414 @@ file { "/tmp/yayness": result = parser.parse %{$variable = undef} } - children = result.children - assert_instance_of(AST::VarDef, result.children[0]) - assert_instance_of(AST::Undef, result.children[0].value) + main = result.classes[""].code + children = main.children + assert_instance_of(AST::VarDef, main.children[0]) + assert_instance_of(AST::Undef, main.children[0].value) + end + + # Prompted by #729 -- parsing should not modify the interpreter. + def test_parse + parser = mkparser + + str = "file { '/tmp/yay': ensure => file }\nclass yay {}\nnode foo {}\ndefine bar {}\n" + result = nil + assert_nothing_raised("Could not parse") do + result = parser.parse(str) + end + assert_instance_of(Puppet::Parser::Parser::ASTSet, result, "Did not get a ASTSet back from parsing") + + assert_instance_of(AST::HostClass, result.classes["yay"], "Did not create 'yay' class") + assert_instance_of(AST::HostClass, result.classes[""], "Did not create main class") + assert_instance_of(AST::Component, result.definitions["bar"], "Did not create 'bar' definition") + assert_instance_of(AST::Node, result.nodes["foo"], "Did not create 'foo' node") + end + + # Make sure our node gets added to the node table. + def test_newnode + parser = mkparser + + # First just try calling it directly + assert_nothing_raised { + parser.newnode("mynode", :code => :yay) + } + + assert_equal(:yay, parser.nodes["mynode"].code) + + # Now make sure that trying to redefine it throws an error. + assert_raise(Puppet::ParseError) { + parser.newnode("mynode", {}) + } + + # Now try one with no code + assert_nothing_raised { + parser.newnode("simplenode", :parent => :foo) + } + + # Now define the parent node + parser.newnode(:foo) + + # And make sure we get things back correctly + assert_equal(:foo, parser.nodes["simplenode"].parentclass) + assert_nil(parser.nodes["simplenode"].code) + + # Now make sure that trying to redefine it throws an error. + assert_raise(Puppet::ParseError) { + parser.newnode("mynode", {}) + } + + # Test multiple names + names = ["one", "two", "three"] + assert_nothing_raised { + parser.newnode(names, {:code => :yay, :parent => :foo}) + } + + names.each do |name| + assert_equal(:yay, parser.nodes[name].code) + assert_equal(:foo, parser.nodes[name].parentclass) + # Now make sure that trying to redefine it throws an error. + assert_raise(Puppet::ParseError) { + parser.newnode(name, {}) + } + end + end + + def test_newdefine + parser = mkparser + + assert_nothing_raised { + parser.newdefine("mydefine", :code => :yay, + :arguments => ["a", stringobj("b")]) + } + + mydefine = parser.definitions["mydefine"] + assert(mydefine, "Could not find definition") + assert_equal("", mydefine.namespace) + assert_equal("mydefine", mydefine.classname) + + assert_raise(Puppet::ParseError) do + parser.newdefine("mydefine", :code => :yay, + :arguments => ["a", stringobj("b")]) + end + + # Now define the same thing in a different scope + assert_nothing_raised { + parser.newdefine("other::mydefine", :code => :other, + :arguments => ["a", stringobj("b")]) + } + other = parser.definitions["other::mydefine"] + assert(other, "Could not find definition") + assert(parser.definitions["other::mydefine"], + "Could not find other::mydefine") + assert_equal(:other, other.code) + assert_equal("other", other.namespace) + assert_equal("other::mydefine", other.classname) + end + + def test_newclass + parser = mkparser + + mkcode = proc do |ary| + classes = ary.collect do |string| + AST::FlatString.new(:value => string) + end + AST::ASTArray.new(:children => classes) + end + + scope = Puppet::Parser::Scope.new(:interp => mkinterp) + + # First make sure that code is being appended + code = mkcode.call(%w{original code}) + + klass = nil + assert_nothing_raised { + klass = parser.newclass("myclass", :code => code) + } + + assert(klass, "Did not return class") + + assert(parser.classes["myclass"], "Could not find definition") + assert_equal("myclass", parser.classes["myclass"].classname) + assert_equal(%w{original code}, + parser.classes["myclass"].code.evaluate(:scope => scope)) + + # Newclass behaves differently than the others -- it just appends + # the code to the existing class. + code = mkcode.call(%w{something new}) + assert_nothing_raised do + klass = parser.newclass("myclass", :code => code) + end + assert(klass, "Did not return class when appending") + assert_equal(%w{original code something new}, + parser.classes["myclass"].code.evaluate(:scope => scope)) + + # Now create the same class name in a different scope + assert_nothing_raised { + klass = parser.newclass("other::myclass", + :code => mkcode.call(%w{something diff})) + } + assert(klass, "Did not return class") + other = parser.classes["other::myclass"] + assert(other, "Could not find class") + assert_equal("other::myclass", other.classname) + assert_equal("other::myclass", other.namespace) + assert_equal(%w{something diff}, + other.code.evaluate(:scope => scope)) + + # Make sure newclass deals correctly with nodes with no code + klass = parser.newclass("nocode") + assert(klass, "Did not return class") + + assert_nothing_raised do + klass = parser.newclass("nocode", :code => mkcode.call(%w{yay test})) + end + assert(klass, "Did not return class with no code") + assert_equal(%w{yay test}, + parser.classes["nocode"].code.evaluate(:scope => scope)) + + # Then try merging something into nothing + parser.newclass("nocode2", :code => mkcode.call(%w{foo test})) + assert(klass, "Did not return class with no code") + + assert_nothing_raised do + klass = parser.newclass("nocode2") + end + assert(klass, "Did not return class with no code") + assert_equal(%w{foo test}, + parser.classes["nocode2"].code.evaluate(:scope => scope)) + + # And lastly, nothing and nothing + klass = parser.newclass("nocode3") + assert(klass, "Did not return class with no code") + + assert_nothing_raised do + klass = parser.newclass("nocode3") + end + assert(klass, "Did not return class with no code") + assert_nil(parser.classes["nocode3"].code) + end + + # Make sure you can't have classes and defines with the same name in the + # same scope. + def test_classes_beat_defines + parser = mkparser + + assert_nothing_raised { + parser.newclass("yay::funtest") + } + + assert_raise(Puppet::ParseError) do + parser.newdefine("yay::funtest") + end + + assert_nothing_raised { + parser.newdefine("yay::yaytest") + } + + assert_raise(Puppet::ParseError) do + parser.newclass("yay::yaytest") + end + end + + def test_namesplit + parser = mkparser + + assert_nothing_raised do + {"base::sub" => %w{base sub}, + "main" => ["", "main"], + "one::two::three::four" => ["one::two::three", "four"], + }.each do |name, ary| + result = parser.namesplit(name) + assert_equal(ary, result, "%s split to %s" % [name, result]) + end + end + end + + # Now make sure we get appropriate behaviour with parent class conflicts. + def test_newclass_parentage + parser = mkparser + parser.newclass("base1") + parser.newclass("one::two::three") + + # First create it with no parentclass. + assert_nothing_raised { + parser.newclass("sub") + } + assert(parser.classes["sub"], "Could not find definition") + assert_nil(parser.classes["sub"].parentclass) + + # Make sure we can't set the parent class to ourself. + assert_raise(Puppet::ParseError) { + parser.newclass("sub", :parent => "sub") + } + + # Now create another one, with a parentclass. + assert_nothing_raised { + parser.newclass("sub", :parent => "base1") + } + + # Make sure we get the right parent class, and make sure it's not an object. + assert_equal("base1", + parser.classes["sub"].parentclass) + + # Now make sure we get a failure if we try to conflict. + assert_raise(Puppet::ParseError) { + parser.newclass("sub", :parent => "one::two::three") + } + + # Make sure that failure didn't screw us up in any way. + assert_equal("base1", + parser.classes["sub"].parentclass) + # But make sure we can create a class with a fq parent + assert_nothing_raised { + parser.newclass("another", :parent => "one::two::three") + } + assert_equal("one::two::three", + parser.classes["another"].parentclass) + + end + + def test_fqfind + parser = mkparser + + table = {} + # Define a bunch of things. + %w{a c a::b a::b::c a::c a::b::c::d a::b::c::d::e::f c::d}.each do |string| + table[string] = string + end + + check = proc do |namespace, hash| + hash.each do |thing, result| + assert_equal(result, parser.fqfind(namespace, thing, table), + "Could not find %s in %s" % [thing, namespace]) + end + end + + # Now let's do some test lookups. + + # First do something really simple + check.call "a", "b" => "a::b", "b::c" => "a::b::c", "d" => nil, "::c" => "c" + + check.call "a::b", "c" => "a::b::c", "b" => "a::b", "a" => "a" + + check.call "a::b::c::d::e", "c" => "a::b::c", "::c" => "c", + "c::d" => "a::b::c::d", "::c::d" => "c::d" + + check.call "", "a" => "a", "a::c" => "a::c" + end + + # Setup a module. + def mk_module(name, files = {}) + mdir = File.join(@dir, name) + mandir = File.join(mdir, "manifests") + FileUtils.mkdir_p mandir + + if defs = files[:define] + files.delete(:define) + end + Dir.chdir(mandir) do + files.each do |file, classes| + File.open("%s.pp" % file, "w") do |f| + classes.each { |klass| + if defs + f.puts "define %s {}" % klass + else + f.puts "class %s {}" % klass + end + } + end + end + end + end + + # #596 - make sure classes and definitions load automatically if they're in modules, so we don't have to manually load each one. + def test_module_autoloading + @dir = tempfile + Puppet[:modulepath] = @dir + + FileUtils.mkdir_p @dir + + parser = mkparser + + # Make sure we fail like normal for actually missing classes + assert_nil(parser.findclass("", "nosuchclass"), "Did not return nil on missing classes") + + # test the simple case -- the module class itself + name = "simple" + mk_module(name, :init => [name]) + + # Try to load the module automatically now + klass = parser.findclass("", name) + assert_instance_of(AST::HostClass, klass, "Did not autoload class from module init file") + assert_equal(name, klass.classname, "Incorrect class was returned") + + # Try loading the simple module when we're in something other than the base namespace. + parser = mkparser + klass = parser.findclass("something::else", name) + assert_instance_of(AST::HostClass, klass, "Did not autoload class from module init file") + assert_equal(name, klass.classname, "Incorrect class was returned") + + # Now try it with a definition as the base file + name = "simpdef" + mk_module(name, :define => true, :init => [name]) + + klass = parser.finddefine("", name) + assert_instance_of(AST::Component, klass, "Did not autoload class from module init file") + assert_equal(name, klass.classname, "Incorrect class was returned") + + # Now try it with namespace classes where both classes are in the init file + parser = mkparser + modname = "both" + name = "sub" + mk_module(modname, :init => %w{both both::sub}) + + # First try it with a namespace + klass = parser.findclass("both", name) + assert_instance_of(AST::HostClass, klass, "Did not autoload sub class from module init file with a namespace") + assert_equal("both::sub", klass.classname, "Incorrect class was returned") + + # Now try it using the fully qualified name + parser = mkparser + klass = parser.findclass("", "both::sub") + assert_instance_of(AST::HostClass, klass, "Did not autoload sub class from module init file with no namespace") + assert_equal("both::sub", klass.classname, "Incorrect class was returned") + + + # Now try it with the class in a different file + parser = mkparser + modname = "separate" + name = "sub" + mk_module(modname, :init => %w{separate}, :sub => %w{separate::sub}) + + # First try it with a namespace + klass = parser.findclass("separate", name) + assert_instance_of(AST::HostClass, klass, "Did not autoload sub class from separate file with a namespace") + assert_equal("separate::sub", klass.classname, "Incorrect class was returned") + + # Now try it using the fully qualified name + parser = mkparser + klass = parser.findclass("", "separate::sub") + assert_instance_of(AST::HostClass, klass, "Did not autoload sub class from separate file with no namespace") + assert_equal("separate::sub", klass.classname, "Incorrect class was returned") + + # Now make sure we don't get a failure when there's no module file + parser = mkparser + modname = "alone" + name = "sub" + mk_module(modname, :sub => %w{alone::sub}) + + # First try it with a namespace + assert_nothing_raised("Could not autoload file when module file is missing") do + klass = parser.findclass("alone", name) + end + assert_instance_of(AST::HostClass, klass, "Did not autoload sub class from alone file with a namespace") + assert_equal("alone::sub", klass.classname, "Incorrect class was returned") + + # Now try it using the fully qualified name + parser = mkparser + klass = parser.findclass("", "alone::sub") + assert_instance_of(AST::HostClass, klass, "Did not autoload sub class from alone file with no namespace") + assert_equal("alone::sub", klass.classname, "Incorrect class was returned") end end diff --git a/test/lib/puppettest/parsertesting.rb b/test/lib/puppettest/parsertesting.rb index 0cd308181..d66367ada 100644 --- a/test/lib/puppettest/parsertesting.rb +++ b/test/lib/puppettest/parsertesting.rb @@ -46,7 +46,7 @@ module PuppetTest::ParserTesting end def mkparser - Puppet::Parser::Parser.new(mkinterp) + Puppet::Parser::Parser.new() end def mkscope(hash = {}) diff --git a/test/lib/puppettest/support/collection.rb b/test/lib/puppettest/support/collection.rb index 69feb5077..305b805d5 100644 --- a/test/lib/puppettest/support/collection.rb +++ b/test/lib/puppettest/support/collection.rb @@ -18,7 +18,7 @@ module PuppetTest::Support::Collection query = nil assert_nothing_raised("Could not parse '#{str}'") do - query = parser.parse(code)[0].query + query = parser.parse(code).classes[""].code[0].query end yield str, res, query |
