diff --git a/lib/puppet/parser/files.rb b/lib/puppet/parser/files.rb index 9ef05e102..f34683153 100644 --- a/lib/puppet/parser/files.rb +++ b/lib/puppet/parser/files.rb @@ -1,88 +1,88 @@ require 'puppet/module' require 'puppet/parser/parser' # This is a silly central module for finding # different kinds of files while parsing. This code # doesn't really belong in the Puppet::Module class, # but it doesn't really belong anywhere else, either. module Puppet::Parser::Files module_function # Return a list of manifests (as absolute filenames) that match +pat+ # with the current directory set to +cwd+. If the first component of # +pat+ does not contain any wildcards and is an existing module, return # a list of manifests in that module matching the rest of +pat+ # Otherwise, try to find manifests matching +pat+ relative to +cwd+ def find_manifests(start, options = {}) cwd = options[:cwd] || Dir.getwd module_name, pattern = split_file_path(start) begin if mod = Puppet::Module.find(module_name, options[:environment]) return [mod.name, mod.match_manifests(pattern)] end rescue Puppet::Module::InvalidName # Than that would be a "no." end abspat = File::expand_path(start, cwd) - [nil, Dir.glob(abspat + (File.extname(abspat).empty? ? '{,.pp,.rb}' : '' )).uniq.reject { |f| FileTest.directory?(f) }] + [nil, Dir.glob(abspat + (File.extname(abspat).empty? ? '{.pp,.rb}' : '' )).uniq.reject { |f| FileTest.directory?(f) }] end # Find the concrete file denoted by +file+. If +file+ is absolute, # return it directly. Otherwise try to find it as a template in a # module. If that fails, return it relative to the +templatedir+ config # param. # In all cases, an absolute path is returned, which does not # necessarily refer to an existing file def find_template(template, environment = nil) if template == File.expand_path(template) return template end if template_paths = templatepath(environment) # If we can find the template in :templatedir, we return that. template_paths.collect { |path| File::join(path, template) }.each do |f| return f if FileTest.exist?(f) end end # check in the default template dir, if there is one if td_file = find_template_in_module(template, environment) return td_file end nil end def find_template_in_module(template, environment = nil) path, file = split_file_path(template) # Because templates don't have an assumed template name, like manifests do, # we treat templates with no name as being templates in the main template # directory. return nil unless file if mod = Puppet::Module.find(path, environment) and t = mod.template(file) return t end nil end # Return an array of paths by splitting the +templatedir+ config # parameter. def templatepath(environment = nil) dirs = Puppet.settings.value(:templatedir, environment).split(File::PATH_SEPARATOR) dirs.select do |p| File::directory?(p) end end # Split the path into the module and the rest of the path, or return # nil if the path is empty or absolute (starts with a /). # This method can return nil & anyone calling it needs to handle that. def split_file_path(path) path.split(File::SEPARATOR, 2) unless path == "" or path == File.expand_path(path) end end diff --git a/spec/unit/parser/files_spec.rb b/spec/unit/parser/files_spec.rb index fcfbfa613..3eb0db07e 100644 --- a/spec/unit/parser/files_spec.rb +++ b/spec/unit/parser/files_spec.rb @@ -1,201 +1,201 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../../spec_helper' require 'puppet/parser/files' describe Puppet::Parser::Files do before do @basepath = Puppet.features.posix? ? "/somepath" : "C:/somepath" end it "should have a method for finding a template" do Puppet::Parser::Files.should respond_to(:find_template) end it "should have a method for finding manifests" do Puppet::Parser::Files.should respond_to(:find_manifests) end describe "when searching for templates" do it "should return fully-qualified templates directly" do Puppet::Parser::Files.expects(:modulepath).never Puppet::Parser::Files.find_template(@basepath + "/my/template").should == @basepath + "/my/template" end it "should return the template from the first found module" do mod = mock 'module' Puppet::Node::Environment.new.expects(:module).with("mymod").returns mod mod.expects(:template).returns("/one/mymod/templates/mytemplate") Puppet::Parser::Files.find_template("mymod/mytemplate").should == "/one/mymod/templates/mytemplate" end it "should return the file in the templatedir if it exists" do Puppet.settings.expects(:value).with(:templatedir, nil).returns("/my/templates") Puppet[:modulepath] = "/one:/two" File.stubs(:directory?).returns(true) FileTest.stubs(:exist?).returns(true) Puppet::Parser::Files.find_template("mymod/mytemplate").should == "/my/templates/mymod/mytemplate" end it "should not raise an error if no valid templatedir exists and the template exists in a module" do mod = mock 'module' Puppet::Node::Environment.new.expects(:module).with("mymod").returns mod mod.expects(:template).returns("/one/mymod/templates/mytemplate") Puppet::Parser::Files.stubs(:templatepath).with(nil).returns(nil) Puppet::Parser::Files.find_template("mymod/mytemplate").should == "/one/mymod/templates/mytemplate" end it "should return unqualified templates if they exist in the template dir" do FileTest.stubs(:exist?).returns true Puppet::Parser::Files.stubs(:templatepath).with(nil).returns(["/my/templates"]) Puppet::Parser::Files.find_template("mytemplate").should == "/my/templates/mytemplate" end it "should only return templates if they actually exist" do FileTest.expects(:exist?).with("/my/templates/mytemplate").returns true Puppet::Parser::Files.stubs(:templatepath).with(nil).returns(["/my/templates"]) Puppet::Parser::Files.find_template("mytemplate").should == "/my/templates/mytemplate" end it "should return nil when asked for a template that doesn't exist" do FileTest.expects(:exist?).with("/my/templates/mytemplate").returns false Puppet::Parser::Files.stubs(:templatepath).with(nil).returns(["/my/templates"]) Puppet::Parser::Files.find_template("mytemplate").should be_nil end it "should search in the template directories before modules" do FileTest.stubs(:exist?).returns true Puppet::Parser::Files.stubs(:templatepath).with(nil).returns(["/my/templates"]) Puppet::Module.expects(:find).never Puppet::Parser::Files.find_template("mytemplate") end it "should accept relative templatedirs" do FileTest.stubs(:exist?).returns true Puppet[:templatedir] = "my/templates" File.expects(:directory?).with(File.join(Dir.getwd,"my/templates")).returns(true) Puppet::Parser::Files.find_template("mytemplate").should == File.join(Dir.getwd,"my/templates/mytemplate") end it "should use the environment templatedir if no module is found and an environment is specified" do FileTest.stubs(:exist?).returns true Puppet::Parser::Files.stubs(:templatepath).with("myenv").returns(["/myenv/templates"]) Puppet::Parser::Files.find_template("mymod/mytemplate", "myenv").should == "/myenv/templates/mymod/mytemplate" end it "should use first dir from environment templatedir if no module is found and an environment is specified" do FileTest.stubs(:exist?).returns true Puppet::Parser::Files.stubs(:templatepath).with("myenv").returns(["/myenv/templates", "/two/templates"]) Puppet::Parser::Files.find_template("mymod/mytemplate", "myenv").should == "/myenv/templates/mymod/mytemplate" end it "should use a valid dir when templatedir is a path for unqualified templates and the first dir contains template" do Puppet::Parser::Files.stubs(:templatepath).returns(["/one/templates", "/two/templates"]) FileTest.expects(:exist?).with("/one/templates/mytemplate").returns(true) Puppet::Parser::Files.find_template("mytemplate").should == "/one/templates/mytemplate" end it "should use a valid dir when templatedir is a path for unqualified templates and only second dir contains template" do Puppet::Parser::Files.stubs(:templatepath).returns(["/one/templates", "/two/templates"]) FileTest.expects(:exist?).with("/one/templates/mytemplate").returns(false) FileTest.expects(:exist?).with("/two/templates/mytemplate").returns(true) Puppet::Parser::Files.find_template("mytemplate").should == "/two/templates/mytemplate" end it "should use the node environment if specified" do mod = mock 'module' Puppet::Node::Environment.new("myenv").expects(:module).with("mymod").returns mod mod.expects(:template).returns("/my/modules/mymod/templates/envtemplate") Puppet::Parser::Files.find_template("mymod/envtemplate", "myenv").should == "/my/modules/mymod/templates/envtemplate" end it "should return nil if no template can be found" do Puppet::Parser::Files.find_template("foomod/envtemplate", "myenv").should be_nil end after { Puppet.settings.clear } end describe "when searching for manifests" do it "should ignore invalid modules" do mod = mock 'module' Puppet::Node::Environment.new.expects(:module).with("mymod").raises(Puppet::Module::InvalidName, "name is invalid") Puppet.expects(:value).with(:modulepath).never Dir.stubs(:glob).returns %w{foo} Puppet::Parser::Files.find_manifests("mymod/init.pp") end end describe "when searching for manifests when no module is found" do before do File.stubs(:find).returns(nil) end it "should not look for modules when paths are fully qualified" do Puppet.expects(:value).with(:modulepath).never file = @basepath + "/fully/qualified/file.pp" Dir.stubs(:glob).with(file).returns([file]) Puppet::Parser::Files.find_manifests(file) end it "should return nil and an array of fully qualified files" do file = @basepath + "/fully/qualified/file.pp" Dir.stubs(:glob).with(file).returns([file]) Puppet::Parser::Files.find_manifests(file).should == [nil, [file]] end it "should match against provided fully qualified patterns" do pattern = @basepath + "/fully/qualified/pattern/*" - Dir.expects(:glob).with(pattern+'{,.pp,.rb}').returns(%w{my file list}) + Dir.expects(:glob).with(pattern+'{.pp,.rb}').returns(%w{my file list}) Puppet::Parser::Files.find_manifests(pattern)[1].should == %w{my file list} end it "should look for files relative to the current directory" do cwd = Dir.getwd Dir.expects(:glob).with("#{cwd}/foobar/init.pp").returns(["#{cwd}/foobar/init.pp"]) Puppet::Parser::Files.find_manifests("foobar/init.pp")[1].should == ["#{cwd}/foobar/init.pp"] end it "should only return files, not directories" do pattern = @basepath + "/fully/qualified/pattern/*" file = @basepath + "/my/file" dir = @basepath + "/my/directory" - Dir.expects(:glob).with(pattern+'{,.pp,.rb}').returns([file, dir]) + Dir.expects(:glob).with(pattern+'{.pp,.rb}').returns([file, dir]) FileTest.expects(:directory?).with(file).returns(false) FileTest.expects(:directory?).with(dir).returns(true) Puppet::Parser::Files.find_manifests(pattern)[1].should == [file] end it "should return files once only" do pattern = @basepath + "/fully/qualified/pattern/*" - Dir.expects(:glob).with(pattern+'{,.pp,.rb}').returns(%w{one two one}) + Dir.expects(:glob).with(pattern+'{.pp,.rb}').returns(%w{one two one}) Puppet::Parser::Files.find_manifests(pattern)[1].should == %w{one two} end end describe "when searching for manifests in a found module" do it "should return the name of the module and the manifests from the first found module" do mod = Puppet::Module.new("mymod") Puppet::Node::Environment.new.expects(:module).with("mymod").returns mod mod.expects(:match_manifests).with("init.pp").returns(%w{/one/mymod/manifests/init.pp}) Puppet::Parser::Files.find_manifests("mymod/init.pp").should == ["mymod", ["/one/mymod/manifests/init.pp"]] end it "should use the node environment if specified" do mod = Puppet::Module.new("mymod") Puppet::Node::Environment.new("myenv").expects(:module).with("mymod").returns mod mod.expects(:match_manifests).with("init.pp").returns(%w{/one/mymod/manifests/init.pp}) Puppet::Parser::Files.find_manifests("mymod/init.pp", :environment => "myenv")[1].should == ["/one/mymod/manifests/init.pp"] end after { Puppet.settings.clear } end end diff --git a/spec/unit/parser/type_loader_spec.rb b/spec/unit/parser/type_loader_spec.rb index 8f005d551..83006b37b 100644 --- a/spec/unit/parser/type_loader_spec.rb +++ b/spec/unit/parser/type_loader_spec.rb @@ -1,201 +1,201 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../../spec_helper' require 'puppet/parser/type_loader' require 'puppet_spec/files' describe Puppet::Parser::TypeLoader do include PuppetSpec::Files before do @loader = Puppet::Parser::TypeLoader.new(:myenv) end it "should support an environment" do loader = Puppet::Parser::TypeLoader.new(:myenv) loader.environment.name.should == :myenv end it "should include the Environment Helper" do @loader.class.ancestors.should be_include(Puppet::Node::Environment::Helper) end it "should delegate its known resource types to its environment" do @loader.known_resource_types.should be_instance_of(Puppet::Resource::TypeCollection) end describe "when loading names from namespaces" do it "should do nothing if the name to import is an empty string" do @loader.expects(:name2files).never @loader.load_until(["foo"], "") { |f| false }.should be_nil end it "should turn the provided namespaces and name into a list of files" do @loader.expects(:name2files).with(["foo"], "bar").returns [] @loader.load_until(["foo"], "bar") { |f| false } end it "should attempt to import each generated name" do @loader.expects(:name2files).returns %w{foo bar} @loader.expects(:import).with("foo",nil) @loader.expects(:import).with("bar",nil) @loader.load_until(["foo"], "bar") { |f| false } end it "should yield after each import" do yielded = [] @loader.expects(:name2files).returns %w{foo bar} @loader.expects(:import).with("foo",nil) @loader.expects(:import).with("bar",nil) @loader.load_until(["foo"], "bar") { |f| yielded << f; false } yielded.should == %w{foo bar} end it "should stop importing when the yielded block returns true" do yielded = [] @loader.expects(:name2files).returns %w{foo bar baz} @loader.expects(:import).with("foo",nil) @loader.expects(:import).with("bar",nil) @loader.expects(:import).with("baz",nil).never @loader.load_until(["foo"], "bar") { |f| true if f == "bar" } end it "should return the result of the block" do yielded = [] @loader.expects(:name2files).returns %w{foo bar baz} @loader.expects(:import).with("foo",nil) @loader.expects(:import).with("bar",nil) @loader.expects(:import).with("baz",nil).never @loader.load_until(["foo"], "bar") { |f| 10 if f == "bar" }.should == 10 end it "should return nil if the block never returns true" do @loader.expects(:name2files).returns %w{foo bar} @loader.expects(:import).with("foo",nil) @loader.expects(:import).with("bar",nil) @loader.load_until(["foo"], "bar") { |f| false }.should be_nil end it "should know when a given name has been loaded" do @loader.expects(:name2files).returns %w{file} @loader.expects(:import).with("file",nil) @loader.load_until(["foo"], "bar") { |f| true } @loader.should be_loaded("file") end it "should set the module name on any created resource types" do type = Puppet::Resource::Type.new(:hostclass, "mytype") Puppet::Parser::Files.expects(:find_manifests).returns ["modname", %w{one}] @loader.stubs(:parse_file) @loader.load_until(["foo"], "one") { |f| type } type.module_name.should == "modname" end end describe "when mapping names to files" do { [["foo"], "::bar::baz"] => %w{bar/baz}, [[""], "foo::bar"] => %w{foo foo/bar}, [%w{foo}, "bar"] => %w{foo foo/bar bar}, [%w{a b}, "bar"] => %w{a a/bar b b/bar bar}, [%w{a::b::c}, "bar"] => %w{a a/b/c/bar bar}, [%w{a::b}, "foo::bar"] => %w{a a/b/foo/bar foo/bar} }.each do |inputs, outputs| it "should produce #{outputs.inspect} from the #{inputs[0].inspect} namespace and #{inputs[1]} name" do @loader.name2files(*inputs).should == outputs end end end describe "when importing" do before do Puppet::Parser::Files.stubs(:find_manifests).returns ["modname", %w{file}] @loader.stubs(:parse_file) end it "should return immediately when imports are being ignored" do Puppet::Parser::Files.expects(:find_manifests).never Puppet[:ignoreimport] = true @loader.import("foo").should be_nil end it "should find all manifests matching the file or pattern" do Puppet::Parser::Files.expects(:find_manifests).with { |pat, opts| pat == "myfile" }.returns ["modname", %w{one}] @loader.import("myfile") end it "should use the directory of the current file if one is set" do Puppet::Parser::Files.expects(:find_manifests).with { |pat, opts| opts[:cwd] == "/current" }.returns ["modname", %w{one}] @loader.import("myfile", "/current/file") end it "should pass the environment when looking for files" do Puppet::Parser::Files.expects(:find_manifests).with { |pat, opts| opts[:environment] == @loader.environment }.returns ["modname", %w{one}] @loader.import("myfile") end it "should fail if no files are found" do Puppet::Parser::Files.expects(:find_manifests).returns [nil, []] lambda { @loader.import("myfile") }.should raise_error(Puppet::ImportError) end it "should parse each found file" do Puppet::Parser::Files.expects(:find_manifests).returns ["modname", %w{/one}] @loader.expects(:parse_file).with("/one") @loader.import("myfile") end it "should make each file qualified before attempting to parse it" do Puppet::Parser::Files.expects(:find_manifests).returns ["modname", %w{one}] @loader.expects(:parse_file).with("/current/one") @loader.import("myfile", "/current/file") end it "should know when a given file has been imported" do Puppet::Parser::Files.expects(:find_manifests).returns ["modname", %w{/one}] @loader.import("myfile") @loader.should be_imported("/one") end it "should not attempt to import files that have already been imported" do Puppet::Parser::Files.expects(:find_manifests).returns ["modname", %w{/one}] @loader.expects(:parse_file).once @loader.import("myfile") # This will fail if it tries to reimport the file. @loader.import("myfile") end end describe "when parsing a file" do before do @parser = Puppet::Parser::Parser.new(@loader.environment) @parser.stubs(:parse) @parser.stubs(:file=) Puppet::Parser::Parser.stubs(:new).with(@loader.environment).returns @parser end it "should create a new parser instance for each file using the current environment" do Puppet::Parser::Parser.expects(:new).with(@loader.environment).returns @parser @loader.parse_file("/my/file") end it "should assign the parser its file and parse" do @parser.expects(:file=).with("/my/file") @parser.expects(:parse) @loader.parse_file("/my/file") end end it "should be able to add classes to the current resource type collection" do - file = tmpfile("simple_file") + file = tmpfile("simple_file.pp") File.open(file, "w") { |f| f.puts "class foo {}" } @loader.import(file) @loader.known_resource_types.hostclass("foo").should be_instance_of(Puppet::Resource::Type) end end diff --git a/test/language/parser.rb b/test/language/parser.rb index 5a433c724..8cda8eeb2 100755 --- a/test/language/parser.rb +++ b/test/language/parser.rb @@ -1,746 +1,746 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../lib/puppettest' require 'mocha' require 'puppet' require 'puppet/parser/parser' require 'puppettest' require 'puppettest/support/utils' class TestParser < Test::Unit::TestCase include PuppetTest include PuppetTest::ParserTesting include PuppetTest::Support::Utils def setup super Puppet[:parseonly] = true #@lexer = Puppet::Parser::Lexer.new end def teardown super Puppet::Node::Environment.clear end def test_each_file textfiles { |file| Puppet::Node::Environment.clear parser = mkparser Puppet.debug("parsing #{file}") if __FILE__ == $0 assert_nothing_raised { parser.file = file parser.parse } } end def test_failers failers { |file| parser = mkparser Puppet.debug("parsing failer #{file}") if __FILE__ == $0 assert_raise(Puppet::ParseError, "Did not fail while parsing #{file}") { parser.file = file ast = parser.parse config = mkcompiler(parser) config.compile #ast.hostclass("").evaluate config.topscope } } end def test_arrayrvalues parser = mkparser ret = nil file = tempfile assert_nothing_raised { parser.string = "file { \"#{file}\": mode => [755, 640] }" } assert_nothing_raised { ret = parser.parse } end def test_arrayrvalueswithtrailingcomma parser = mkparser ret = nil file = tempfile assert_nothing_raised { parser.string = "file { \"#{file}\": mode => [755, 640,] }" } assert_nothing_raised { ret = parser.parse } end def mkmanifest(file) name = File.join(tmpdir, "file#{rand(100)}") @@tmpfiles << name File.open(file, "w") { |f| f.puts "file { \"%s\": ensure => file, mode => 755 }\n" % name } end def test_importglobbing basedir = File.join(tmpdir, "importesting") @@tmpfiles << basedir Dir.mkdir(basedir) subdir = "subdir" Dir.mkdir(File.join(basedir, subdir)) manifest = File.join(basedir, "manifest") File.open(manifest, "w") { |f| f.puts "import \"%s/*\"" % subdir } 4.times { |i| - path = File.join(basedir, subdir, "subfile#{i}") + path = File.join(basedir, subdir, "subfile#{i}.pp") mkmanifest(path) } assert_nothing_raised("Could not parse multiple files") { parser = mkparser parser.file = manifest parser.parse } end def test_nonexistent_import basedir = File.join(tmpdir, "importesting") @@tmpfiles << basedir Dir.mkdir(basedir) manifest = File.join(basedir, "manifest") File.open(manifest, "w") do |f| f.puts "import \" no such file \"" end assert_raise(Puppet::ParseError) { parser = mkparser parser.file = manifest parser.parse } end def test_trailingcomma path = tempfile str = %{file { "#{path}": ensure => file, } } parser = mkparser parser.string = str assert_nothing_raised("Could not parse trailing comma") { parser.parse } end def test_importedclasses - imported = tempfile - importer = tempfile + imported = tempfile '.pp' + importer = tempfile '.pp' made = tempfile File.open(imported, "w") do |f| f.puts %{class foo { file { "#{made}": ensure => file }}} end File.open(importer, "w") do |f| f.puts %{import "#{imported}"\ninclude foo} end parser = mkparser parser.file = importer # Make sure it parses fine assert_nothing_raised { parser.parse } # Now make sure it actually does the work assert_creates(importer, made) end # Make sure fully qualified and unqualified files can be imported def test_fqfilesandlocalfiles dir = tempfile Dir.mkdir(dir) importer = File.join(dir, "site.pp") fullfile = File.join(dir, "full.pp") localfile = File.join(dir, "local.pp") files = [] File.open(importer, "w") do |f| f.puts %{import "#{fullfile}"\ninclude full\nimport "local.pp"\ninclude local} end fullmaker = tempfile files << fullmaker File.open(fullfile, "w") do |f| f.puts %{class full { file { "#{fullmaker}": ensure => file }}} end localmaker = tempfile files << localmaker File.open(localfile, "w") do |f| f.puts %{class local { file { "#{localmaker}": ensure => file }}} end parser = mkparser parser.file = importer # Make sure it parses assert_nothing_raised { parser.parse } # Now make sure it actually does the work assert_creates(importer, *files) end # Make sure the parser adds '.pp' when necessary def test_addingpp dir = tempfile Dir.mkdir(dir) importer = File.join(dir, "site.pp") localfile = File.join(dir, "local.pp") files = [] File.open(importer, "w") do |f| f.puts %{import "local"\ninclude local} end file = tempfile files << file File.open(localfile, "w") do |f| f.puts %{class local { file { "#{file}": ensure => file }}} end parser = mkparser parser.file = importer assert_nothing_raised { parser.parse } end # Make sure that file importing changes file relative names. def test_changingrelativenames dir = tempfile Dir.mkdir(dir) Dir.mkdir(File.join(dir, "subdir")) top = File.join(dir, "site.pp") subone = File.join(dir, "subdir/subone") subtwo = File.join(dir, "subdir/subtwo") files = [] file = tempfile files << file File.open(subone + ".pp", "w") do |f| f.puts %{class one { file { "#{file}": ensure => file }}} end otherfile = tempfile files << otherfile File.open(subtwo + ".pp", "w") do |f| f.puts %{import "subone"\n class two inherits one { file { "#{otherfile}": ensure => file } }} end File.open(top, "w") do |f| f.puts %{import "subdir/subtwo"} end parser = mkparser parser.file = top assert_nothing_raised { parser.parse } end # Defaults are purely syntactical, so it doesn't make sense to be able to # collect them. def test_uncollectabledefaults string = "@Port { protocols => tcp }" assert_raise(Puppet::ParseError) { mkparser.parse(string) } end # Verify that we can parse collections def test_collecting text = "Port <| |>" parser = mkparser parser.string = text ret = nil assert_nothing_raised { ret = parser.parse } ret.hostclass("").code.each do |obj| assert_instance_of(AST::Collection, obj) end end def test_emptyfile file = tempfile File.open(file, "w") do |f| f.puts %{} end parser = mkparser parser.file = file assert_nothing_raised { parser.parse } end def test_multiple_nodes_named file = tempfile other = tempfile File.open(file, "w") do |f| f.puts %{ node nodeA, nodeB { file { "#{other}": ensure => file } } } end parser = mkparser parser.file = file ast = nil assert_nothing_raised { ast = parser.parse } end def test_emptyarrays str = %{$var = []\n} parser = mkparser parser.string = str # Make sure it parses fine assert_nothing_raised { parser.parse } end # Make sure function names aren't reserved words. def test_functionnamecollision str = %{tag yayness tag(rahness) file { "/tmp/yayness": tag => "rahness", ensure => exists } } parser = mkparser parser.string = str # Make sure it parses fine assert_nothing_raised { parser.parse } end def test_metaparams_in_definition_prototypes parser = mkparser assert_raise(Puppet::ParseError) { parser.parse %{define mydef($schedule) {}} } assert_nothing_raised { parser.parse %{define adef($schedule = false) {}} parser.parse %{define mydef($schedule = daily) {}} } end def test_parsingif parser = mkparser exec = proc do |val| %{exec { "/bin/echo #{val}": logoutput => true }} end str1 = %{if true { #{exec.call("true")} }} ret = nil assert_nothing_raised { ret = parser.parse(str1).hostclass("").code[0] } assert_instance_of(Puppet::Parser::AST::IfStatement, ret) parser = mkparser str2 = %{if true { #{exec.call("true")} } else { #{exec.call("false")} }} ret = parser.parse(str2).hostclass("").code[0] assert_instance_of(Puppet::Parser::AST::IfStatement, ret) assert_instance_of(Puppet::Parser::AST::Else, ret.else) end def test_hostclass parser = mkparser assert_nothing_raised { parser.parse %{class myclass { class other {} }} } assert(parser.hostclass("myclass"), "Could not find myclass") assert(parser.hostclass("myclass::other"), "Could not find myclass::other") assert_nothing_raised { parser.parse "class base {} class container { class deep::sub inherits base {} }" } sub = parser.hostclass("container::deep::sub") assert(sub, "Could not find sub") # Now try it with a parent class being a fq class assert_nothing_raised { parser.parse "class container::one inherits container::deep::sub {}" } sub = parser.hostclass("container::one") assert(sub, "Could not find one") assert_equal("container::deep::sub", sub.parent) # Finally, try including a qualified class assert_nothing_raised("Could not include fully qualified class") { parser.parse "include container::deep::sub" } end def test_topnamespace parser = mkparser # Make sure we put the top-level code into a class called "" in # the "" namespace assert_nothing_raised do out = parser.parse "" assert_instance_of(Puppet::Resource::TypeCollection, out) assert_nil(parser.hostclass(""), "Got a 'main' class when we had no code") end # Now try something a touch more complicated parser.initvars assert_nothing_raised do out = parser.parse "Exec { path => '/usr/bin:/usr/sbin' }" assert_instance_of(Puppet::Resource::TypeCollection, out) assert_equal("", parser.hostclass("").name) assert_equal("", parser.hostclass("").namespace) end end # Make sure virtual and exported resources work appropriately. def test_virtualresources tests = [:virtual] if Puppet.features.rails? catalog_cache_class = Puppet::Resource::Catalog.indirection.cache_class facts_cache_class = Puppet::Node::Facts.indirection.cache_class node_cache_class = Puppet::Node.indirection.cache_class Puppet[:storeconfigs] = true tests << :exported end tests.each do |form| parser = mkparser if form == :virtual at = "@" else at = "@@" end check = proc do |res, msg| if res.is_a?(Puppet::Parser::Resource) txt = res.ref else txt = res.class end # Real resources get marked virtual when exported if form == :virtual or res.is_a?(Puppet::Parser::Resource) assert(res.virtual, "#{msg} #{at}#{txt} is not virtual") end if form == :virtual assert(! res.exported, "#{msg} #{at}#{txt} is exported") else assert(res.exported, "#{msg} #{at}#{txt} is not exported") end end ret = nil assert_nothing_raised do ret = parser.parse("#{at}file { '/tmp/testing': owner => root }") end assert_instance_of(AST::ASTArray, ret.hostclass("").code) resdef = ret.hostclass("").code[0] assert_instance_of(AST::Resource, resdef) assert_equal("/tmp/testing", resdef.title.value) # We always get an astarray back, so... 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 ret.hostclass("").code.each do |res| assert_instance_of(AST::Resource, res) check.call(res, "multiresource") end end ensure if Puppet.features.rails? Puppet[:storeconfigs] = false Puppet::Resource::Catalog.cache_class = catalog_cache_class Puppet::Node::Facts.cache_class = facts_cache_class Puppet::Node.cache_class = node_cache_class end end def test_collections tests = [:virtual] if Puppet.features.rails? catalog_cache_class = Puppet::Resource::Catalog.indirection.cache_class facts_cache_class = Puppet::Node::Facts.indirection.cache_class node_cache_class = Puppet::Node.indirection.cache_class Puppet[:storeconfigs] = true tests << :exported end tests.each do |form| Puppet::Node::Environment.clear parser = mkparser if form == :virtual arrow = "<||>" else arrow = "<<||>>" end ret = nil assert_nothing_raised do ret = parser.parse("File #{arrow}") end coll = ret.hostclass("").code[0] assert_instance_of(AST::Collection, coll) assert_equal(form, coll.form) end ensure if Puppet.features.rails? Puppet[:storeconfigs] = false Puppet::Resource::Catalog.cache_class = catalog_cache_class Puppet::Node::Facts.cache_class = facts_cache_class Puppet::Node.cache_class = node_cache_class end end def test_collectionexpressions %w{== !=}.each do |oper| Puppet::Node::Environment.clear str = "File <| title #{oper} '/tmp/testing' |>" parser = mkparser res = nil assert_nothing_raised do res = parser.parse(str).hostclass("").code[0] end assert_instance_of(AST::Collection, res) query = res.query assert_instance_of(AST::CollExpr, query) assert_equal(:virtual, query.form) assert_equal("title", query.test1.value) assert_equal("/tmp/testing", query.test2.value) assert_equal(oper, query.oper) end end def test_collectionstatements %w{and or}.each do |joiner| str = "File <| title == '/tmp/testing' #{joiner} owner == root |>" parser = mkparser res = nil assert_nothing_raised do res = parser.parse(str).hostclass("").code[0] end assert_instance_of(AST::Collection, res) query = res.query assert_instance_of(AST::CollExpr, query) assert_equal(joiner, query.oper) assert_instance_of(AST::CollExpr, query.test1) assert_instance_of(AST::CollExpr, query.test2) end end def test_collectionstatements_with_parens [ "(title == '/tmp/testing' and owner == root) or owner == wheel", "(title == '/tmp/testing')" ].each do |test| str = "File <| #{test} |>" parser = mkparser res = nil assert_nothing_raised("Could not parse '#{test}'") do res = parser.parse(str).hostclass("").code[0] end assert_instance_of(AST::Collection, res) query = res.query assert_instance_of(AST::CollExpr, query) #assert_equal(joiner, query.oper) #assert_instance_of(AST::CollExpr, query.test1) #assert_instance_of(AST::CollExpr, query.test2) end end def test_fully_qualified_definitions parser = mkparser assert_nothing_raised("Could not parse fully-qualified definition") { parser.parse %{define one::two { }} } assert(parser.definition("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") { parser.parse %{one::two { yayness: }} } end # #524 def test_functions_with_no_arguments parser = mkparser assert_nothing_raised("Could not parse statement function with no args") { parser.parse %{tag()} } assert_nothing_raised("Could not parse rvalue function with no args") { parser.parse %{$testing = template()} } end # #774 def test_fully_qualified_collection_statement parser = mkparser assert_nothing_raised("Could not parse fully qualified collection statement") { parser.parse %{Foo::Bar <||>} } end def test_multiple_imports_on_one_line - one = tempfile - two = tempfile - base = tempfile + one = tempfile '.pp' + two = tempfile '.pp' + base = tempfile '.pp' File.open(one, "w") { |f| f.puts "$var = value" } File.open(two, "w") { |f| f.puts "$var = value" } File.open(base, "w") { |f| f.puts "import '#{one}', '#{two}'" } parser = mkparser parser.file = base # Importing is logged at debug time. Puppet::Util::Log.level = :debug assert_nothing_raised("Parser could not import multiple files at once") do parser.parse end [one, two].each do |file| assert(@logs.detect { |l| l.message =~ /importing '#{file}'/}, "did not import #{file}") end end def test_cannot_assign_qualified_variables parser = mkparser assert_raise(Puppet::ParseError, "successfully assigned a qualified variable") do parser.parse("$one::two = yay") end end # #629 - undef keyword def test_undef parser = mkparser result = nil assert_nothing_raised("Could not parse assignment to undef") { result = parser.parse %{$variable = undef} } main = result.hostclass("").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::Resource::TypeCollection, result, "Did not get a ASTSet back from parsing") assert_instance_of(Puppet::Resource::Type, result.hostclass("yay"), "Did not create 'yay' class") assert_instance_of(Puppet::Resource::Type, result.hostclass(""), "Did not create main class") assert_instance_of(Puppet::Resource::Type, result.definition("bar"), "Did not create 'bar' definition") assert_instance_of(Puppet::Resource::Type, result.node("foo"), "Did not create 'foo' node") 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, "#{name} split to #{result}") end end end # Make sure class, node, and define methods are case-insensitive def test_structure_case_insensitivity parser = mkparser result = nil assert_nothing_raised do result = parser.newclass "Yayness" end assert_equal(result, parser.find_hostclass("", "yayNess")) assert_nothing_raised do result = parser.newdefine "FunTest" end assert_equal(result, parser.find_definition("", "fUntEst"), "#{"fUntEst"} was not matched") end end diff --git a/test/lib/puppettest.rb b/test/lib/puppettest.rb index e31a31902..294d0ef8d 100755 --- a/test/lib/puppettest.rb +++ b/test/lib/puppettest.rb @@ -1,339 +1,339 @@ # Add .../test/lib testlib = File.expand_path(File.dirname(__FILE__)) $LOAD_PATH.unshift(testlib) unless $LOAD_PATH.include?(testlib) # Add .../lib mainlib = File.expand_path(File.join(File.dirname(__FILE__), '../../lib')) $LOAD_PATH.unshift(mainlib) unless $LOAD_PATH.include?(mainlib) require 'puppet' # include any gems in vendor/gems Dir["#{mainlib}/../vendor/gems/**"].each do |path| libpath = File.join(path, "lib") if File.directory?(libpath) $LOAD_PATH.unshift(libpath) else $LOAD_PATH.unshift(path) end end require 'mocha' # Only load the test/unit class if we're not in the spec directory. # Else we get the bogus 'no tests, no failures' message. unless Dir.getwd =~ /spec/ require 'test/unit' end # Yay; hackish but it works if ARGV.include?("-d") ARGV.delete("-d") $console = true end # Some monkey-patching to allow us to test private methods. class Class def publicize_methods(*methods) saved_private_instance_methods = methods.empty? ? self.private_instance_methods : methods self.class_eval { public(*saved_private_instance_methods) } yield self.class_eval { private(*saved_private_instance_methods) } end end module PuppetTest # These need to be here for when rspec tests use these # support methods. @@tmpfiles = [] # Munge cli arguments, so we can enable debugging if we want # and so we can run just specific methods. def self.munge_argv require 'getoptlong' result = GetoptLong.new( [ "--debug", "-d", GetoptLong::NO_ARGUMENT ], [ "--resolve", "-r", GetoptLong::REQUIRED_ARGUMENT ], [ "-n", GetoptLong::REQUIRED_ARGUMENT ], [ "--help", "-h", GetoptLong::NO_ARGUMENT ] ) usage = "USAGE: TESTOPTS='[-n -n ...] [-d]' rake [target] [target] ..." opts = [] dir = method = nil result.each { |opt,arg| case opt when "--resolve" dir, method = arg.split(",") when "--debug" $puppet_debug = true Puppet::Util::Log.level = :debug Puppet::Util::Log.newdestination(:console) when "--help" puts usage exit else opts << opt << arg end } suites = nil args = ARGV.dup # Reset the options, so the test suite can deal with them (this is # what makes things like '-n' work). opts.each { |o| ARGV << o } args end # Find the root of the Puppet tree; this is not the test directory, but # the parent of that dir. def basedir(*list) unless defined?(@@basedir) Dir.chdir(File.dirname(__FILE__)) do @@basedir = File.dirname(File.dirname(Dir.getwd)) end end if list.empty? @@basedir else File.join(@@basedir, *list) end end def datadir(*list) File.join(basedir, "test", "data", *list) end def exampledir(*args) @@exampledir = File.join(basedir, "examples") unless defined?(@@exampledir) if args.empty? return @@exampledir else return File.join(@@exampledir, *args) end end module_function :basedir, :datadir, :exampledir def cleanup(&block) @@cleaners << block end # Rails clobbers RUBYLIB, thanks def libsetup curlibs = ENV["RUBYLIB"].split(":") $LOAD_PATH.reject do |dir| dir =~ /^\/usr/ end.each do |dir| curlibs << dir unless curlibs.include?(dir) end ENV["RUBYLIB"] = curlibs.join(":") end def logcollector collector = [] Puppet::Util::Log.newdestination(collector) cleanup do Puppet::Util::Log.close(collector) end collector end def rake? $0 =~ /test_loader/ end # Redirect stdout and stderr def redirect @stderr = tempfile @stdout = tempfile $stderr = File.open(@stderr, "w") $stdout = File.open(@stdout, "w") cleanup do $stderr = STDERR $stdout = STDOUT end end def setup ENV["PATH"] += File::PATH_SEPARATOR + "/usr/sbin" unless ENV["PATH"].split(File::PATH_SEPARATOR).include?("/usr/sbin") @memoryatstart = Puppet::Util.memory if defined?(@@testcount) @@testcount += 1 else @@testcount = 0 end @configpath = File.join( tmpdir, "configdir" + @@testcount.to_s + "/" ) unless defined? $user and $group $user = nonrootuser.uid.to_s $group = nonrootgroup.gid.to_s end Puppet.settings.clear Puppet[:user] = $user Puppet[:group] = $group Puppet[:confdir] = @configpath Puppet[:vardir] = @configpath Dir.mkdir(@configpath) unless File.exists?(@configpath) @@tmpfiles << @configpath << tmpdir @@tmppids = [] @@cleaners = [] @logs = [] # If we're running under rake, then disable debugging and such. #if rake? or ! Puppet[:debug] #if defined?($puppet_debug) or ! rake? Puppet[:color] = false if textmate? Puppet::Util::Log.newdestination(@logs) if defined? $console Puppet.info @method_name Puppet::Util::Log.newdestination(:console) Puppet[:trace] = true end Puppet::Util::Log.level = :debug #$VERBOSE = 1 #else # Puppet::Util::Log.close # Puppet::Util::Log.newdestination(@logs) # Puppet[:httplog] = tempfile #end Puppet[:ignoreschedules] = true #@start = Time.now #Facter.stubs(:value).returns "stubbed_value" #Facter.stubs(:to_hash).returns({}) end - def tempfile + def tempfile(suffix = '') if defined?(@@tmpfilenum) @@tmpfilenum += 1 else @@tmpfilenum = 1 end - f = File.join(self.tmpdir, "tempfile_" + @@tmpfilenum.to_s) + f = File.join(self.tmpdir, "tempfile_" + @@tmpfilenum.to_s + suffix) @@tmpfiles ||= [] @@tmpfiles << f f end def textmate? !!ENV["TM_FILENAME"] end def tstdir dir = tempfile Dir.mkdir(dir) dir end def tmpdir unless @tmpdir @tmpdir = case Facter["operatingsystem"].value when "Darwin"; "/private/tmp" when "SunOS"; "/var/tmp" else "/tmp" end @tmpdir = File.join(@tmpdir, "puppettesting#{Process.pid}") unless File.exists?(@tmpdir) FileUtils.mkdir_p(@tmpdir) File.chmod(01777, @tmpdir) end end @tmpdir end def remove_tmp_files @@tmpfiles.each { |file| unless file =~ /tmp/ puts "Not deleting tmpfile #{file}" next end if FileTest.exists?(file) system("chmod -R 755 #{file}") system("rm -rf #{file}") end } @@tmpfiles.clear end def teardown #@stop = Time.now #File.open("/tmp/test_times.log", ::File::WRONLY|::File::CREAT|::File::APPEND) { |f| f.puts "%0.4f %s %s" % [@stop - @start, @method_name, self.class] } @@cleaners.each { |cleaner| cleaner.call } remove_tmp_files @@tmppids.each { |pid| %x{kill -INT #{pid} 2>/dev/null} } @@tmppids.clear Puppet::Util::Storage.clear Puppet.clear Puppet.settings.clear Puppet::Util::Cacher.expire @memoryatend = Puppet::Util.memory diff = @memoryatend - @memoryatstart Puppet.info "#{self.class}##{@method_name} memory growth (#{@memoryatstart} to #{@memoryatend}): #{diff}" if diff > 1000 # reset all of the logs Puppet::Util::Log.close_all @logs.clear # Just in case there are processes waiting to die... require 'timeout' begin Timeout::timeout(5) do Process.waitall end rescue Timeout::Error # just move on end end def logstore @logs = [] Puppet::Util::Log.newdestination(@logs) end end require 'puppettest/support' require 'puppettest/filetesting' require 'puppettest/fakes' require 'puppettest/exetest' require 'puppettest/parsertesting' require 'puppettest/servertest' require 'puppettest/testcase'