#!/usr/bin/env ruby

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

require 'puppet'
require 'puppet/parser/interpreter'
require 'puppet/parser/parser'
require 'puppet/network/client'
require 'puppettest'
require 'puppettest/resourcetesting'

class TestLangFunctions < Test::Unit::TestCase
    include PuppetTest::ParserTesting
    include PuppetTest::ResourceTesting
    def test_functions
        assert_raise(Puppet::ParseError) do
            Puppet::Parser::AST::Function.new(
                :name => "fakefunction",
                :arguments => AST::ASTArray.new(
                    :children => [nameobj("avalue")]
                )
            )
        end

        assert_nothing_raised do
            Puppet::Parser::Functions.newfunction(:fakefunction, :type => :rvalue) do |input|
                return "output %s" % input[0]
            end
        end

        func = nil
        assert_nothing_raised do
            func = Puppet::Parser::AST::Function.new(
                :name => "fakefunction",
                :ftype => :rvalue,
                :arguments => AST::ASTArray.new(
                    :children => [nameobj("avalue")]
                )
            )
        end

        scope = mkscope
        val = nil
        assert_nothing_raised do
            val = func.evaluate(:scope => scope)
        end

        assert_equal("output avalue", val)
    end

    def test_taggedfunction
        scope = mkscope

        tag = "yayness"
        scope.tag(tag)

        {"yayness" => true, "booness" => false}.each do |tag, retval|
            func = taggedobj(tag, :rvalue)

            val = nil
            assert_nothing_raised do
                val = func.evaluate(:scope => scope)
            end

            assert_equal(retval, val, "'tagged' returned %s for %s" % [val, tag])
        end
    end

    def test_failfunction
        func = nil
        assert_nothing_raised do
            func = Puppet::Parser::AST::Function.new(
                :name => "fail",
                :ftype => :statement,
                :arguments => AST::ASTArray.new(
                    :children => [stringobj("this is a failure"),
                        stringobj("and another")]
                )
            )
        end

        scope = mkscope
        val = nil
        assert_raise(Puppet::ParseError) do
            val = func.evaluate(:scope => scope)
        end
    end

    def test_multipletemplates
        Dir.mkdir(Puppet[:templatedir])
        onep = File.join(Puppet[:templatedir], "one")
        twop = File.join(Puppet[:templatedir], "two")

        File.open(onep, "w") do |f|
            f.puts "template <%= one %>"
        end

        File.open(twop, "w") do |f|
            f.puts "template <%= two %>"
        end
        func = nil
        assert_nothing_raised do
            func = Puppet::Parser::AST::Function.new(
                :name => "template",
                :ftype => :rvalue,
                :arguments => AST::ASTArray.new(
                    :children => [stringobj("one"),
                        stringobj("two")]
                )
            )
        end
        ast = varobj("output", func)

        scope = mkscope
        assert_raise(Puppet::ParseError) do
            ast.evaluate(:scope => scope)
        end

        scope.setvar("one", "One")
        assert_raise(Puppet::ParseError) do
            ast.evaluate(:scope => scope)
        end
        scope.setvar("two", "Two")
        assert_nothing_raised do
            ast.evaluate(:scope => scope)
        end

        assert_equal("template One\ntemplate Two\n", scope.lookupvar("output"),
            "Templates were not handled correctly")
    end

    # Now make sure we can fully qualify files, and specify just one
    def test_singletemplates
        template = tempfile()

        File.open(template, "w") do |f|
            f.puts "template <%= yayness %>"
        end

        func = nil
        assert_nothing_raised do
            func = Puppet::Parser::AST::Function.new(
                :name => "template",
                :ftype => :rvalue,
                :arguments => AST::ASTArray.new(
                    :children => [stringobj(template)]
                )
            )
        end
        ast = varobj("output", func)

        scope = mkscope
        assert_raise(Puppet::ParseError) do
            ast.evaluate(:scope => scope)
        end

        scope.setvar("yayness", "this is yayness")

        assert_nothing_raised do
            ast.evaluate(:scope => scope)
        end

        assert_equal("template this is yayness\n", scope.lookupvar("output"),
            "Templates were not handled correctly")

    end

    def test_tempatefunction_cannot_see_scopes
        template = tempfile()

        File.open(template, "w") do |f|
            f.puts "<%= lookupvar('myvar') %>"
        end

        func = nil
        assert_nothing_raised do
            func = Puppet::Parser::AST::Function.new(
                :name => "template",
                :ftype => :rvalue,
                :arguments => AST::ASTArray.new(
                    :children => [stringobj(template)]
                )
            )
        end
        ast = varobj("output", func)

        scope = mkscope
        scope.setvar("myvar", "this is yayness")
        assert_raise(Puppet::ParseError) do
            ast.evaluate(:scope => scope)
        end
    end

    def test_template_reparses
        template = tempfile()

        File.open(template, "w") do |f|
            f.puts "original text"
        end

        manifest = tempfile()
        file = tempfile()
        File.open(manifest, "w") do |f|
            f.puts %{file { "#{file}": content => template("#{template}") }}
        end

        interpreter = Puppet::Parser::Interpreter.new(
            :Manifest => manifest,
            :UseNodes => false
        )

        parsedate = interpreter.parsedate()

        objects = nil
        assert_nothing_raised {
            objects = interpreter.run("myhost", {})
        }

        fileobj = objects[0]

        assert_equal("original text\n", fileobj["content"],
            "Template did not work")

        Puppet[:filetimeout] = 0
        # Have to sleep because one second is the fs's time granularity.
        sleep(1)

        # Now modify the template
        File.open(template, "w") do |f|
            f.puts "new text"
        end

        assert_nothing_raised {
            objects = interpreter.run("myhost", {})
        }
        newdate = interpreter.parsedate()

        assert(parsedate != newdate, "Parse date did not change")
    end

    def test_template_defined_vars
        template = tempfile()

        File.open(template, "w") do |f|
            f.puts "template <%= yayness %>"
        end

        func = nil
        assert_nothing_raised do
            func = Puppet::Parser::AST::Function.new(
                :name => "template",
                :ftype => :rvalue,
                :arguments => AST::ASTArray.new(
                    :children => [stringobj(template)]
                )
            )
        end
        ast = varobj("output", func)

        {
            "" => "",
            false => "false",
        }.each do |string, value|
            scope = mkscope
            assert_raise(Puppet::ParseError) do
                ast.evaluate(:scope => scope)
            end

            scope.setvar("yayness", string)
            assert_equal(string, scope.lookupvar("yayness", false))

            assert_nothing_raised("An empty string was not a valid variable value") do
                ast.evaluate(:scope => scope)
            end

            assert_equal("template #{value}\n", scope.lookupvar("output"),
                         "%s did not get evaluated correctly" % string.inspect)
        end
    end

    def test_autoloading_functions
        assert_equal(false, Puppet::Parser::Functions.function(:autofunc),
            "Got told autofunc already exists")

        dir = tempfile()
        $: << dir
        newpath = File.join(dir, "puppet", "parser", "functions")
        FileUtils.mkdir_p(newpath)

        File.open(File.join(newpath, "autofunc.rb"), "w") { |f|
            f.puts %{
                Puppet::Parser::Functions.newfunction(:autofunc, :type => :rvalue) do |vals|
                    Puppet.wanring vals.inspect
                end
            }
        }

        obj = nil
        assert_nothing_raised {
            obj = Puppet::Parser::Functions.function(:autofunc)
        }

        assert(obj, "Did not autoload function")
        assert(Puppet::Parser::Scope.method_defined?(:function_autofunc),
            "Did not set function correctly")
    end

    def test_realize
        @interp, @scope, @source = mkclassframing
    
        # Make a definition
        @interp.newdefine("mytype")
        
        [%w{file /tmp/virtual}, %w{mytype yay}].each do |type, title|
            # Make a virtual resource
            virtual = mkresource(:type => type, :title => title,
                :virtual => true, :params => {})
        
            @scope.setresource virtual

            ref = Puppet::Parser::Resource::Reference.new(
                :type => type, :title => title,
                :scope => @scope
            )
            # Now call the realize function
            assert_nothing_raised do
                @scope.function_realize(ref)
            end

            # Make sure it created a collection
            assert_equal(1, @scope.collections.length,
                "Did not set collection")

            assert_nothing_raised do
                @scope.collections.each do |coll| coll.evaluate end
            end
            @scope.collections.clear

            # Now make sure the virtual resource is no longer virtual
            assert(! virtual.virtual?, "Did not make virtual resource real")
        end

        # Make sure we puke on any resource that doesn't exist
        none = Puppet::Parser::Resource::Reference.new(
            :type => "file", :title => "/tmp/nosuchfile",
            :scope => @scope
        )

        # The function works
        assert_nothing_raised do
            @scope.function_realize(none.to_s)
        end

        # Make sure it created a collection
        assert_equal(1, @scope.collections.length,
            "Did not set collection")

        # And the collection has our resource in it
        assert_equal([none.to_s], @scope.collections[0].resources,
            "Did not set resources in collection")
    end
    
    def test_defined
        interp = mkinterp
        scope = mkscope(:interp => interp)
        
        interp.newclass("yayness")
        interp.newdefine("rahness")
        
        assert_nothing_raised do
            assert(scope.function_defined("yayness"), "yayness class was not considered defined")
            assert(scope.function_defined("rahness"), "rahness definition was not considered defined")
            assert(scope.function_defined("service"), "service type was not considered defined")
            assert(! scope.function_defined("fakness"), "fakeness was considered defined")
        end
        
        # Now make sure any match in a list will work
        assert(scope.function_defined(["booness", "yayness", "fakeness"]),
            "A single answer was not sufficient to return true")
        
        # and make sure multiple falses are still false
        assert(! scope.function_defined(%w{no otherno stillno}),
            "Multiple falses were somehow true")
        
        # Now make sure we can test resources
        scope.setresource mkresource(:type => "file", :title => "/tmp/rahness",
            :scope => scope, :source => scope.source,
            :params => {:owner => "root"})
        
        yep = Puppet::Parser::Resource::Reference.new(:type => "file", :title => "/tmp/rahness")
        nope = Puppet::Parser::Resource::Reference.new(:type => "file", :title => "/tmp/fooness")
        
        assert(scope.function_defined([yep]), "valid resource was not considered defined")
        assert(! scope.function_defined([nope]), "invalid resource was considered defined")
    end

    def test_search
        interp = mkinterp
        scope = mkscope(:interp => interp)
        
        fun = interp.newdefine("yay::ness")
        foo = interp.newdefine("foo::bar")

        search = Puppet::Parser::Functions.function(:search)
        assert_nothing_raised do
            scope.function_search(["foo", "yay"])
        end

        ffun = ffoo = nil
        assert_nothing_raised("Search path change did not work") do
            ffun = scope.finddefine("ness")
            ffoo = scope.finddefine('bar')
        end

        assert(ffun, "Could not find definition in 'fun' namespace")
        assert(ffoo, "Could not find definition in 'foo' namespace")
    end

    def test_include
        interp = mkinterp
        scope = mkscope(:interp => interp)

        assert_raise(Puppet::ParseError, "did not throw error on missing class") do
            scope.function_include("nosuchclass")
        end

        interp.newclass("myclass")

        assert_nothing_raised do
            scope.function_include "myclass"
        end

        assert(scope.classlist.include?("myclass"),
            "class was not evaluated")

        # Now try multiple classes at once
        classes = %w{one two three}.each { |c| interp.newclass(c) }

        assert_nothing_raised do
            scope.function_include classes
        end

        classes.each do |c|
            assert(scope.classlist.include?(c),
                "class %s was not evaluated" % c)
        end

        # Now try a scoped class
        interp.newclass("os::redhat")

        assert_nothing_raised("Could not include qualified class name") do
            scope.function_include("os::redhat")
        end
    end

    def test_file
        interp = mkinterp
        scope = mkscope(:interp => interp)

        file1 = tempfile
        file2 = tempfile
        file3 = tempfile

        File.open(file2, "w") { |f| f.puts "yaytest" }

        val = nil
        assert_nothing_raised("Failed to call file with one arg") do
            val = scope.function_file([file2])
        end

        assert_equal("yaytest\n", val, "file() failed")

        assert_nothing_raised("Failed to call file with two args") do
            val = scope.function_file([file1, file2])
        end

        assert_equal("yaytest\n", val, "file() failed")

        assert_raise(Puppet::ParseError, "did not fail when files are missing") do
            val = scope.function_file([file1, file3])
        end
    end

    def test_generate
        command = tempfile
        sh = %x{which sh}
        File.open(command, "w") do |f|
            f.puts %{#!#{sh}
            if [ -n "$1" ]; then
                echo "yay-$1"
            else
                echo yay
            fi
            }
        end
        File.chmod(0755, command)
        assert_equal("yay\n", %x{#{command}}, "command did not work")
        assert_equal("yay-foo\n", %x{#{command} foo}, "command did not work")

        interp = mkinterp
        scope = mkscope(:interp => interp)

        val = nil
        assert_nothing_raised("Could not call generator with no args") do
            val = scope.function_generate([command])
        end
        assert_equal("yay\n", val, "generator returned wrong results")

        assert_nothing_raised("Could not call generator with args") do
            val = scope.function_generate([command, "foo"])
        end
        assert_equal("yay-foo\n", val, "generator returned wrong results")

        assert_raise(Puppet::ParseError, "Did not fail with an unqualified path") do
            val = scope.function_generate([File.basename(command), "foo"])
        end

        assert_raise(Puppet::ParseError, "Did not fail when command failed") do
            val = scope.function_generate([%x{which touch}.chomp, "/this/dir/does/not/exist"])
        end

        fake = File.join(File.dirname(command), "..")
        dir = File.dirname(command)
        dirname = File.basename(dir)
        bad = File.join(dir, "..", dirname, File.basename(command))
        assert_raise(Puppet::ParseError, "Did not fail when command failed") do
            val = scope.function_generate([bad])
        end
    end
end

# $Id: functions.rb 2714 2007-07-19 18:12:20Z luke $