#!/usr/bin/env ruby

$:.unshift("../lib").unshift("../../lib") if __FILE__ =~ /\.rb$/

require 'puppet'
require 'puppet/parser/parser'
require 'puppettest'

class TestParser < Test::Unit::TestCase
    include PuppetTest::ParserTesting
    def setup
        super
        Puppet[:parseonly] = true
        #@lexer = Puppet::Parser::Lexer.new()
    end

    def test_each_file
        textfiles { |file|
            parser = mkparser
            Puppet.debug("parsing %s" % file) if __FILE__ == $0
            assert_nothing_raised() {
                parser.file = file
                parser.parse
            }

            Puppet::Type.eachtype { |type|
                type.each { |obj|
                    assert(obj.file, "File is not set on %s" % obj.ref)
                    assert(obj.name, "Name is not set on %s" % obj.ref)
                    assert(obj.line, "Line is not set on %s" % obj.ref)
                }
            }
            Puppet::Type.allclear
        }
    end

    def test_failers
        failers { |file|
            parser = mkparser
            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
            }
            Puppet::Type.allclear
        }
    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 mkmanifest(file)
        name = File.join(tmpdir, "file%s" % 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%s" % i)
            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()

        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
        }

        assert_instance_of(AST::ASTArray, ret)

        ret.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)[0]
        }
        assert_instance_of(Puppet::Parser::AST::IfStatement, ret)
        str2 = %{if true { #{exec.call("true")} } else { #{exec.call("false")} }}
        assert_nothing_raised {
            ret = parser.parse(str2)[0]
        }
        assert_instance_of(Puppet::Parser::AST::IfStatement, ret)
        assert_instance_of(Puppet::Parser::AST::Else, ret.else)
    end

    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_nothing_raised {
            parser.parse "class base {}
            class container {
                class deep::sub inherits base {}
            }"
        }
        sub = interp.findclass("", "container::deep::sub")
        assert(sub, "Could not find sub")
        assert_equal("base", sub.parentclass.type)
        
        # 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")
        assert(sub, "Could not find one")
        assert_equal("sub", sub.parentclass.type)
        
        # 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
        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("", ""))
        end

        # Now try something a touch more complicated
        parser.interp.clear
        assert_nothing_raised do
            out = parser.parse "Exec { path => '/usr/bin:/usr/sbin' }"
            assert_instance_of(AST::ASTArray, out)
            assert_equal("", parser.interp.findclass("", "").type)
            assert_equal("", parser.interp.findclass("", "").namespace)
            assert_equal(out.object_id, parser.interp.findclass("", "").code.object_id)
        end
    end

    # Make sure virtual and exported resources work appropriately.
    def test_virtualresources
        tests = [:virtual]
        if Puppet.features.rails?
            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_equal("/tmp/testing", ret[0].title.value)
            # We always get an astarray back, so...
            assert_instance_of(AST::ResourceDef, ret[0])
            check.call(ret[0], "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|
                assert_instance_of(AST::ResourceDef, res)
                check.call(res, "multiresource")
            end

            # Now evaluate these
            scope = mkscope

            klass = scope.interp.newclass ""
            scope.source = klass

            assert_nothing_raised do
                ret.evaluate :scope => scope
            end

            # Make sure we can find both of them
            %w{/tmp/1 /tmp/2}.each do |title|
                res = scope.findresource("File[#{title}]")
                assert(res, "Could not find %s" % title)
                check.call(res, "found multiresource")
            end
        end
    end

    def test_collections
        tests = [:virtual]
        if Puppet.features.rails?
            Puppet[:storeconfigs] = true
            tests << :exported
        end

        tests.each do |form|
            parser = mkparser

            if form == :virtual
                arrow = "<||>"
            else
                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])
        end
    end

    def test_collectionexpressions
        %w{== !=}.each do |oper|
            str = "File <| title #{oper} '/tmp/testing' |>"

            parser = mkparser

            res = nil
            assert_nothing_raised do
                res = parser.parse(str)[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)[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)[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

    # We've had problems with files other than site.pp importing into main.
    def test_importing_into_main
        top = tempfile()
        other = tempfile()
        File.open(top, "w") do |f|
            f.puts "import '#{other}'"
        end

        file = tempfile()
        File.open(other, "w") do |f|
            f.puts "file { '#{file}': ensure => present }"
        end

        interp = mkinterp :Manifest => top, :UseNodes => false

        code = nil
        assert_nothing_raised do
            code = interp.run("hostname.domain.com", {}).flatten
        end
        assert(code.length == 1, "Did not get the file")
        assert_instance_of(Puppet::TransObject, code[0])
    end
    
    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")
        
        # 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

    def test_module_import
        basedir = File.join(tmpdir(), "module-import")
        @@tmpfiles << basedir
        Dir.mkdir(basedir)
        modfiles = [ "init.pp", "mani1.pp", "mani2.pp",
                     "sub/smani1.pp", "sub/smani2.pp" ]

        modpath = File.join(basedir, "modules")
        Puppet[:modulepath] = modpath

        modname = "amod"
        manipath = File::join(modpath, modname, Puppet::Module::MANIFESTS)
        FileUtils::mkdir_p(File::join(manipath, "sub"))
        targets = []
        modfiles.each do |fname|
            target = File::join(basedir, File::basename(fname, '.pp'))
            targets << target
            txt = %[ file { '#{target}': content => "#{fname}" } ]
            if fname == "init.pp"
                txt = %[import 'mani1' \nimport '#{modname}/mani2'\nimport '#{modname}/sub/*.pp' ] + txt
            end
            File::open(File::join(manipath, fname), "w") do |f|
                f.puts txt
            end
        end

        manifest_texts = [ "import '#{modname}'",
                           "import '#{modname}/init'",
                           "import '#{modname}/init.pp'" ]

        manifest = File.join(modpath, "manifest.pp")
        manifest_texts.each do |txt|
            File.open(manifest, "w") { |f| f.puts txt }

            assert_nothing_raised {
                parser = mkparser
                parser.file = manifest
                parser.parse
            }
            assert_creates(manifest, *targets)
        end
    end

    # #544
    def test_ignoreimports
        parser = mkparser

        assert(! Puppet[:ignoreimport], ":ignoreimport defaulted to true")
        assert_raise(Puppet::ParseError, "Did not fail on missing import") do
            parser.parse("import 'nosuchfile'")
        end
        assert_nothing_raised("could not set :ignoreimport") do
            Puppet[:ignoreimport] = true
        end
        assert_nothing_raised("Parser did not follow :ignoreimports") do
            parser.parse("import 'nosuchfile'")
        end
    end

    def test_multiple_imports_on_one_line
        one = tempfile
        two = tempfile
        base = tempfile
        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 %s" % 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

    # #588
    def test_globbing_with_directories
        dir = tempfile
        Dir.mkdir(dir)
        subdir = File.join(dir, "subdir")
        Dir.mkdir(subdir)
        file = File.join(dir, "file.pp")
        maker = tempfile
        File.open(file, "w") { |f| f.puts "file { '#{maker}': ensure => file }" }

        parser = mkparser
        assert_nothing_raised("Globbing failed when it matched a directory") do
            parser.import("%s/*" % dir)
        end
    end
end

# $Id: parser.rb 2404 2007-04-20 16:40:47Z luke $


syntax highlighted by Code2HTML, v. 0.9.1