summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorluke <luke@980ebf18-57e1-0310-9a29-db15c13687c0>2007-08-03 23:49:53 +0000
committerluke <luke@980ebf18-57e1-0310-9a29-db15c13687c0>2007-08-03 23:49:53 +0000
commit97cd057177f18a0e6694aab0e440f86e0bf08d42 (patch)
tree31a9808aa723180eb43f4fc2c952c3824f89d5d7
parent72f2ac39e036d3fea88569d17f3e455670bba063 (diff)
downloadpuppet-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--CHANGELOG10
-rw-r--r--lib/puppet/parser/ast.rb2
-rw-r--r--lib/puppet/parser/ast/component.rb4
-rw-r--r--lib/puppet/parser/ast/node.rb2
-rw-r--r--lib/puppet/parser/grammar.ra233
-rw-r--r--lib/puppet/parser/interpreter.rb251
-rw-r--r--lib/puppet/parser/parser.rb237
-rw-r--r--lib/puppet/parser/resource/param.rb2
-rwxr-xr-xtest/language/interpreter.rb404
-rwxr-xr-xtest/language/node.rb16
-rwxr-xr-xtest/language/parser.rb487
-rw-r--r--test/lib/puppettest/parsertesting.rb2
-rw-r--r--test/lib/puppettest/support/collection.rb2
13 files changed, 540 insertions, 1112 deletions
diff --git a/CHANGELOG b/CHANGELOG
index 277771466..d20ef06f8 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -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