#!/usr/bin/env ruby

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

require 'puppettest'
require 'etc'

class TestPackageProvider < Test::Unit::TestCase
	include PuppetTest

    # Load the testpackages hash.
    def self.load_test_packages
        require 'yaml'
        file = File.join(PuppetTest.datadir(), "providers", "package", "testpackages.yaml")
        unless FileTest.exists?(file)
            raise "Could not find file %s" % file
        end
        array = YAML::load(File.read(file)).collect { |hash|
            # Stupid ruby 1.8.1.  YAML is sometimes broken such that
            # symbols end up being strings with the : in them.
            hash.each do |name, value|
                if name.is_a?(String) and name =~ /^:/
                    hash.delete(name)
                    name = name.sub(/^:/, '').intern
                    hash[name] = value
                end
                if value.is_a?(String) and value =~ /^:/
                    hash[name] = value.sub(/^:/, '').intern
                end
            end
        }

        return array
    end

    def self.suitable_test_packages
        list = load_test_packages
        providers = {}
        Puppet::Type.type(:package).suitableprovider.each do |provider|
            providers[provider.name] = provider
        end
        facts = {}
        Facter.to_hash.each do |fact, value|
            facts[fact.downcase.intern] = value.downcase.intern
        end
        list.find_all { |hash| # First find the matching providers
            hash.include?(:provider) and providers.include?(hash[:provider])
        }.reject { |hash| # Then find matching fact sets
            facts.detect do |fact, value|
                # We're detecting unmatched facts, but we also want to
                # delete the facts so they don't show up later.
                if fval = hash[fact]
                    hash.delete(fact)
                    fval = [fval] unless fval.is_a?(Array)
                    fval = fval.collect { |v| v.downcase.intern }
                    ! fval.include?(value)
                end
            end
        }
    end

    def assert_absent(provider, msg = "package not absent")
        result = nil
        assert_nothing_raised("Could not query provider") do
            result = provider.query
        end
        if result.nil?
            assert_nil(result)
        elsif result.is_a?(Hash)
            assert_equal(:absent, result[:ensure], msg)
        else
            raise "dunno how to handle %s" % result.inspect
        end
    end

    def assert_not_absent(provider, msg = "package not installed")
        result = nil
        assert_nothing_raised("Could not query provider") do
            result = provider.query
        end
        assert((result == :listed or result.is_a?(Hash)),
            "query did not return hash or :listed")
        if result == :listed
            assert(provider.model.is(:ensure) != :absent, msg)
        else
            assert(result[:ensure] != :absent, msg)
        end
    end

    # Run a package through all of its paces.  FIXME This should use the
    # provider, not the package, duh.
    def run_package_installation_test(hash)
        # Turn the hash into a package
        if files = hash[:files]
            hash.delete(:files)
            if files.is_a?(Array)
                hash[:source] = files.shift
            else
                hash[:source] = files
                files = []
            end
        else
            files = []
        end

        if versions = hash[:versions]
            hash.delete(:versions)
        else
            versions = []
        end

        # Start out by just making sure it's installed
        if versions.empty?
            hash[:ensure] = :present
        else
            hash[:ensure] = versions.shift
        end

        if hash[:source]
            unless FileTest.exists?(hash[:source])
                $stderr.puts "Create a package at %s for testing" % hash[:source]
                return
            end
        end
        
        if cleancmd = hash[:cleanup]
            hash.delete(:cleanup)
        end

        pkg = nil
        assert_nothing_raised(
            "Could not turn %s into a package" % hash.inspect
        ) do
            pkg = Puppet::Type.newpackage(hash)
        end

        # Make any necessary modifications.
        modpkg(pkg)

        provider = pkg.provider

        assert(provider, "Could not retrieve provider")

        assert_absent(provider)

        if Process.uid != 0
            $stderr.puts "Run as root for full package tests"
            return
        end

        cleanup do
            if pkg.provider.respond_to?(:uninstall)
                pkg[:ensure] = :absent
                assert_apply(pkg)
            else
                if cleancmd
                    system(cleancmd)
                end
            end
        end

        assert_nothing_raised("Could not install package") do
            provider.install
        end

        assert_not_absent(provider, "package did not install")

        # If there are any remaining files, then test upgrading from there
        unless files.empty?
            pkg[:source] = files.shift
            current = provider.query
            assert_nothing_raised("Could not upgrade") do
                provider.update
            end
            new = provider.query
            assert(current != new, "package was not upgraded: %s did not change" %
                current.inspect)
        end

        unless versions.empty?
            pkg[:ensure] = versions.shift
            current = provider.query
            assert_nothing_raised("Could not upgrade") do
                provider.update
            end
            new = provider.query
            assert(current != new, "package was not upgraded: %s did not change" %
                current.inspect)
        end

        # Now remove the package
        if provider.respond_to?(:uninstall)
            assert_nothing_raised do
                provider.uninstall
            end

            assert_absent(provider)
        end
    end

    # Now create a separate test method for each package
    suitable_test_packages.each do |hash|
        mname = ["test", hash[:name].to_s, hash[:provider].to_s].join("_").intern

        if method_defined?(mname)
            warn "Already a test method defined for %s" % mname
        else
            define_method(mname) do
                run_package_installation_test(hash)
            end
        end
    end

    def modpkg(pkg)
        case pkg[:provider]
        when :sun:
            pkg[:adminfile] = "/usr/local/pkg/admin_file"
        end
    end

    # Make sure providers throw an error when you tell them to install a
    # non-existent package.
    def test_no_such_package
        Puppet::Type.type(:package).suitableprovider.each do |provider|
            assert_raise(ArgumentError, Puppet::Error, Puppet::ExecutionFailure,
                "Successfully installed nonexistent package with %s" % provider.name) {
                pkg = Puppet::Type.newpackage :name => "nosuch%s" % provider.name.to_s,
                    :provider => provider.name
                provider = pkg.provider
                provider.install
            }
        end
    end

    # Make sure all of the suitable providers on our platform can successfully
    # list.
    def test_listing
        Puppet::Type.type(:package).suitableprovider.each do |provider|
            result = nil
            assert_nothing_raised("Could not list %s packages" % provider.name) do
                result = provider.list
            end
            result.each do |pkg|
                assert_instance_of(Puppet::Type.type(:package), pkg,
                    "%s returned non-package" % provider.name)
                assert_equal(provider.name, pkg.provider.class.name,
                    "%s did not set provider correctly" % provider.name)
            end
        end
    end
end

# $Id: package.rb 2170 2007-02-07 17:21:52Z luke $