<commit_before>#
# Test tpkg's ability to resolve dependencies
#

require File.dirname(__FILE__) + '/tpkgtest'

class TpkgDependencyTests < Test::Unit::TestCase
  include TpkgTests
  
  def setup
    Tpkg::set_prompt(false)
    
    @tempoutdir = Tempdir.new("tempoutdir")  # temp dir that will automatically get deleted at end of test run
                                             # can be used for storing packages
    @pkgfiles = []
    # a depends on b, and c >= 1.1, <= 1.2
    @pkgfiles << make_package(:output_directory => @tempoutdir, :change => { 'name' => 'a' }, :remove => ['operatingsystem', 'architecture'], :dependencies => {'b' => {}, 'c' => {'minimum_version' => '1.1', 'maximum_version' => '1.2'}})
    # generic b for all OSs
    # These two b packages will end up with the same filename, so we
    # manually rename this one
    bpkg = make_package(:output_directory => @tempoutdir, :change => { 'name' => 'b' }, :remove => ['operatingsystem', 'architecture'])
    bpkgnew = bpkg + '.generic'
    File.rename(bpkg, bpkgnew)
    @pkgfiles << bpkgnew
    # b specific to this OS (should prefer this one)
    @pkgfiles << make_package(:output_directory => @tempoutdir, :change => { 'name' => 'b', 'operatingsystem' => Tpkg::get_os }, :remove => ['architecture'])
    # c 1.0 to 1.3, a's dep should result in c-1.2 getting picked
    @pkgfiles << make_package(:output_directory => @tempoutdir, :change => { 'name' => 'c', 'version' => '1.0' }, :remove => ['operatingsystem', 'architecture'], :dependencies => {'d' => {'minimum_version' => '1.0', 'maximum_version' => '1.0'}})
    @pkgfiles << make_package(:output_directory => @tempoutdir, :change => { 'name' => 'c', 'version' => '1.1' }, :remove => ['operatingsystem', 'architecture'], :dependencies => {'d' => {'minimum_version' => '1.1', 'maximum_version' => '1.1'}})
    @pkgfiles << make_package(:output_directory => @tempoutdir, :change => { 'name' => 'c', 'version' => '1.2' }, :remove => ['operatingsystem', 'architecture'], :dependencies => {'d' => {'minimum_version' => '1.2', 'maximum_version' => '1.2'}})
    @pkgfiles << make_package(:output_directory => @tempoutdir, :change => { 'name' => 'c', 'version' => '1.3' }, :remove => ['operatingsystem', 'architecture'], :dependencies => {'d' => {'minimum_version' => '1.3', 'maximum_version' => '1.3'}})
    # d 1.0 to 1.3, c's dep should result in d-1.2 getting picked
    @pkgfiles << make_package(:output_directory => @tempoutdir, :change => { 'name' => 'd', 'version' => '1.0' }, :remove => ['operatingsystem', 'architecture'])
    @pkgfiles << make_package(:output_directory => @tempoutdir, :change => { 'name' => 'd', 'version' => '1.1' }, :remove => ['operatingsystem', 'architecture'])
    @pkgfiles << make_package(:output_directory => @tempoutdir, :change => { 'name' => 'd', 'version' => '1.2' }, :remove => ['operatingsystem', 'architecture'])
    @pkgfiles << make_package(:output_directory => @tempoutdir, :change => { 'name' => 'd', 'version' => '1.3' }, :remove => ['operatingsystem', 'architecture'])
  end
  
  def test_package_meets_requirement
    #
    # Test version handling
    #
    
    pkgfile = make_package(:output_directory => @tempoutdir, :remove => ['operatingsystem', 'architecture'])
    metadata = Tpkg::metadata_from_package(pkgfile)
    pkg = { :metadata => metadata, :source => pkgfile }
    req = { :name => 'testpkg' }
    
    # Below minimum version w/o package version
    req[:minimum_version] = '2.0'
    req[:maximum_version] = '3.0'
    req.delete(:minimum_package_version)
    req.delete(:maximum_package_version)
    assert(!Tpkg::package_meets_requirement?(pkg, req))
    # Below minimum version w/ package version
    req[:minimum_package_version] = '1.0'
    req[:maximum_package_version] = '2.0'
    assert(!Tpkg::package_meets_requirement?(pkg, req))
    # At minimum version w/o package version
    req[:minimum_version] = '1.0'
    req[:maximum_version] = '2.0'
    req.delete(:minimum_package_version)
    req.delete(:maximum_package_version)
    assert(Tpkg::package_meets_requirement?(pkg, req))
    # At minimum version w/ package version
    req[:minimum_package_version] = '1.0'
    req[:maximum_package_version] = '2.0'
    assert(Tpkg::package_meets_requirement?(pkg, req))
    # In middle of range w/o package version
    req[:minimum_version] = '0.5'
    req[:maximum_version] = '2.0'
    req.delete(:minimum_package_version)
    req.delete(:maximum_package_version)
    assert(Tpkg::package_meets_requirement?(pkg, req))
    # In middle of range w/ package version
    req[:minimum_version] = '0.5'
    req[:maximum_version] = '2.0'
    req[:minimum_package_version] = '0.5'
    req[:maximum_package_version] = '2.0'
    assert(Tpkg::package_meets_requirement?(pkg, req))
    # At maximum version w/o package version
    req[:minimum_version] = '0.5'
    req[:maximum_version] = '1.0'
    req.delete(:minimum_package_version)
    req.delete(:maximum_package_version)
    assert(Tpkg::package_meets_requirement?(pkg, req))
    # At maximum version w/ package version
    req[:minimum_version] = '0.5'
    req[:maximum_version] = '1.0'
    req[:minimum_package_version] = '0.5'
    req[:maximum_package_version] = '1.0'
    assert(Tpkg::package_meets_requirement?(pkg, req))
    # Above maximum version w/o package version
    req[:minimum_version] = '0.1'
    req[:maximum_version] = '0.5'
    req.delete(:minimum_package_version)
    req.delete(:maximum_package_version)
    assert(!Tpkg::package_meets_requirement?(pkg, req))
    # Above minimum version w/ package version
    req[:minimum_package_version] = '1.0'
    req[:maximum_package_version] = '2.0'
    assert(!Tpkg::package_meets_requirement?(pkg, req))

    FileUtils.rm_f(pkgfile)

    # More complicated test for PS-375
    pkgfile = make_package(:output_directory => @tempoutdir, :change => {'version' => '2.3', 'package_version' => '2' }, :remove => ['operatingsystem', 'architecture'])
    metadata = Tpkg::metadata_from_package(pkgfile)
    pkg = { :metadata => metadata, :source => pkgfile }
    req = { :name => 'testpkg' }
    # version number is not equal to min or max version. So we don't care if min/max package version satisfied or not
    req[:minimum_version] = '0.5'
    req[:maximum_version] = '3.0'
    req[:minimum_package_version] = '3'
    req[:maximum_package_version] = '3'
    assert(Tpkg::package_meets_requirement?(pkg, req))
    req[:minimum_package_version] = '1'
    req[:maximum_package_version] = '1'
    assert(Tpkg::package_meets_requirement?(pkg, req))
    # version is same as maximum_version, so we have to look at maximum_package_version
    req[:minimum_version] = '0.5'
    req[:maximum_version] = '2.3'
    req[:minimum_package_version] = '1'
    req[:maximum_package_version] = '1'
    assert(!Tpkg::package_meets_requirement?(pkg, req))
    req[:minimum_package_version] = '3'
    req[:maximum_package_version] = '3'
    assert(Tpkg::package_meets_requirement?(pkg, req))
    # version is same as minimum_version, so we have to look at minimum_package_version
    req[:minimum_version] = '2.3'
    req[:maximum_version] = '3.0'
    req[:minimum_package_version] = '3'
    req[:maximum_package_version] = '5'
    assert(!Tpkg::package_meets_requirement?(pkg, req))
    req[:minimum_package_version] = '2'
    req[:maximum_package_version] = '3'
    assert(Tpkg::package_meets_requirement?(pkg, req))

    FileUtils.rm_f(pkgfile)
    
    #
    # Test architecture and operatingsystem handling
    #
    
    req = { :name => 'testpkg' }
    
    # Package with no OS specified
    pkgfile = make_package(:output_directory => @tempoutdir, :remove => ['operatingsystem'], :change => {'architecture' => Facter['hardwaremodel'].value})
    metadata = Tpkg::metadata_from_package(pkgfile)
    pkg = { :metadata => metadata, :source => pkgfile }
    assert(Tpkg::package_meets_requirement?(pkg, req))
    FileUtils.rm_f(pkgfile)
    
    # Package with one matching OS
    pkgfile = make_package(:output_directory => @tempoutdir, :change => {'operatingsystem' => Tpkg::get_os, 'architecture' => Facter['hardwaremodel'].value})
    metadata = Tpkg::metadata_from_package(pkgfile)
    pkg = { :metadata => metadata, :source => pkgfile }
    assert(Tpkg::package_meets_requirement?(pkg, req))
    FileUtils.rm_f(pkgfile)
    
    # Package with a matching OS in a list of OSs
    pkgfile = make_package(:output_directory => @tempoutdir, :change => {'operatingsystem' => "RedHat,CentOS,#{Tpkg::get_os},FreeBSD,Solaris", 'architecture' => Facter['hardwaremodel'].value})
    metadata = Tpkg::metadata_from_package(pkgfile)
    pkg = { :metadata => metadata, :source => pkgfile }
    assert(Tpkg::package_meets_requirement?(pkg, req))
    FileUtils.rm_f(pkgfile)
    
    # Package with one non-matching OS
    pkgfile = make_package(:output_directory => @tempoutdir, :change => {'operatingsystem' => 'bogus_os', 'architecture' => Facter['hardwaremodel'].value})
    metadata = Tpkg::metadata_from_package(pkgfile)
    pkg = { :metadata => metadata, :source => pkgfile }
    assert(!Tpkg::package_meets_requirement?(pkg, req))
    FileUtils.rm_f(pkgfile)
    
    # Package with a list of non-matching OSs
    pkgfile = make_package(:output_directory => @tempoutdir, :change => {'operatingsystem' => 'bogus_os1,bogus_os2', 'architecture' => Facter['hardwaremodel'].value})
    metadata = Tpkg::metadata_from_package(pkgfile)
    pkg = { :metadata => metadata, :source => pkgfile }
    assert(!Tpkg::package_meets_requirement?(pkg, req))
    FileUtils.rm_f(pkgfile)
    
    # Package with no architecture specified
    pkgfile = make_package(:output_directory => @tempoutdir, :remove => ['architecture'], :change => {'operatingsystem' => Tpkg::get_os })
    metadata = Tpkg::metadata_from_package(pkgfile)
    pkg = { :metadata => metadata, :source => pkgfile }
    assert(Tpkg::package_meets_requirement?(pkg, req))
    FileUtils.rm_f(pkgfile)
    
    # Package with one matching architecture
    pkgfile = make_package(:output_directory => @tempoutdir, :change => {'operatingsystem' => Tpkg::get_os, 'architecture' => Facter['hardwaremodel'].value})
    metadata = Tpkg::metadata_from_package(pkgfile)
    pkg = { :metadata => metadata, :source => pkgfile }
    assert(Tpkg::package_meets_requirement?(pkg, req))
    FileUtils.rm_f(pkgfile)
    
    # Package with a matching architecture in a list of architectures
    pkgfile = make_package(:output_directory => @tempoutdir, :change => {'operatingsystem' => Tpkg::get_os, 'architecture' => "i386,x86_64,#{Facter['hardwaremodel'].value},sparc,powerpc"})
    metadata = Tpkg::metadata_from_package(pkgfile)
    pkg = { :metadata => metadata, :source => pkgfile }
    assert(Tpkg::package_meets_requirement?(pkg, req))
    FileUtils.rm_f(pkgfile)
    
    # Package with one non-matching architecture
    pkgfile = make_package(:output_directory => @tempoutdir, :change => {'operatingsystem' => Tpkg::get_os, 'architecture' => 'bogus_arch'})
    metadata = Tpkg::metadata_from_package(pkgfile)
    pkg = { :metadata => metadata, :source => pkgfile }
    assert(!Tpkg::package_meets_requirement?(pkg, req))
    FileUtils.rm_f(pkgfile)
    
    # Package with a list of non-matching architectures
    pkgfile = make_package(:output_directory => @tempoutdir, :change => {'operatingsystem' => Tpkg::get_os, 'architecture' => 'bogus_arch1,bogus_arch2'})
    metadata = Tpkg::metadata_from_package(pkgfile)
    pkg = { :metadata => metadata, :source => pkgfile }
    assert(!Tpkg::package_meets_requirement?(pkg, req))
    FileUtils.rm_f(pkgfile)

    # Package with operatingsystem and arch specified as regex
    pkgfile = make_package(:output_directory => @tempoutdir, :change => {'operatingsystem' => 'RedHat|CentOS|Fedora|Debian|Ubuntu|Solaris|FreeBSD|Darwin',  'architecture' => "i386|x86_64|#{Facter['hardwaremodel'].value}|sparc|powerpc"})
    metadata = Tpkg::metadata_from_package(pkgfile)
    pkg = { :metadata => metadata, :source => pkgfile }
    assert(Tpkg::package_meets_requirement?(pkg, req))
  end
  
  def test_available_packages_that_meet_requirement
    pkgfiles = []
    ['1.0', '1.1', '1.2', '1.3'].each do |ver|
      pkgfiles << make_package(:output_directory => @tempoutdir, :change => {'version' => ver}, :remove => ['operatingsystem', 'architecture'])
    end
    
    testbase = Tempdir.new("testbase")
    tpkg = Tpkg.new(:base => testbase, :sources => pkgfiles)
    
    pkgs = tpkg.available_packages_that_meet_requirement
    nonnativepkgs = pkgs.select do |pkg|
      pkg[:source] != :native_installed && pkg[:source] != :native_available
    end
    assert_equal(4, nonnativepkgs.length)
    
    req = { :name => 'testpkg' }
    
    req[:minimum_version] = '1.2'
    req[:maximum_version] = '2.0'
    pkgs = tpkg.available_packages_that_meet_requirement(req)
    assert_equal(2, pkgs.length)
    pkgs.each do |pkg|
      assert(pkg[:metadata][:version].to_f >= 1.2)
    end
    
    req[:minimum_version] = '1.1'
    req[:maximum_version] = '1.2'
    pkgs = tpkg.available_packages_that_meet_requirement(req)
    assert_equal(2, pkgs.length)
    pkgs.each do |pkg|
      assert(pkg[:metadata][:version].to_f >= 1.1)
      assert(pkg[:metadata][:version].to_f <= 1.2)
    end
    
    # Test a package name which has no available packages
    req[:name] = 'otherpkg'
    pkgs = tpkg.available_packages_that_meet_requirement(req)
    assert(pkgs.empty?)

    # PS-478
    pkgfiles << make_package(:output_directory => @tempoutdir, :change => {'version' => '2'}, :remove => ['operatingsystem', 'architecture', 'package_version'])
    pkgfiles << make_package(:output_directory => @tempoutdir, :change => {'version' => '2', 'package_version' => '1'}, :remove => ['operatingsystem', 'architecture'])
    pkgfiles << make_package(:output_directory => @tempoutdir, :change => {'version' => '2', 'package_version' => '112'}, :remove => ['operatingsystem', 'architecture'])
    tpkg = Tpkg.new(:base => testbase, :sources => pkgfiles)

    req = { :name => 'testpkg' }
    
    # Should only match package of version 2 and NO package version
    req[:allowed_versions] = '2'
    pkgs = tpkg.available_packages_that_meet_requirement(req)
    assert_equal(1, pkgs.length)

    # Should match any packages that has a version number that starts with 2
    req[:allowed_versions] = '2*'
    pkgs = tpkg.available_packages_that_meet_requirement(req)
    assert_equal(3, pkgs.length)

    # Should match any packages that is version 2 AND has a package version number
    req[:allowed_versions] = '2-*'
    pkgs = tpkg.available_packages_that_meet_requirement(req)
    assert_equal(2, pkgs.length)

    # Should match any packages that is version 2 AND has a package version number that ends with 2
    req[:allowed_versions] = '2-*2'
    pkgs = tpkg.available_packages_that_meet_requirement(req)
    assert_equal(1, pkgs.length)

    
    pkgfiles.each { |pkgfile| FileUtils.rm_f(pkgfile) }
    FileUtils.rm_rf(testbase)
  end
  
  def test_best_solution
    # Test that best_solution gives us the right answer using our test
    # package set in a new, clean base
    testbase = Tempdir.new("testbase")
    tpkg = Tpkg.new(:base => testbase, :sources => @pkgfiles)
    solution_packages = tpkg.best_solution([{:name => 'a', :type => :tpkg}], {}, ['a'])
    # We should end up with a-1.0, b-1.0 (the specific one, not the generic
    # one), c-1.2 and d-1.2
    assert_equal(4, solution_packages.length)
    good = ['a-1.0-1.tpkg', "b-1.0-1-#{Metadata.clean_for_filename(Tpkg.get_os)}.tpkg", 'c-1.2-1.tpkg', 'd-1.2-1.tpkg']
    solution_packages.each { |pkg| assert(good.any? { |g| pkg[:source].include?(g) }) }
    FileUtils.rm_rf(testbase)
    
    # Now run a test to verify that we prefer already installed packages
    testbase = Tempdir.new("testbase")
    #  First install an older version of a
    older_apkg = make_package(:output_directory => @tempoutdir, :change => { 'name' => 'a', 'version' => '.9' }, :remove => ['operatingsystem', 'architecture', 'posix_acl', 'windows_acl'])
    tpkg = Tpkg.new(:base => testbase, :sources => [older_apkg] + @pkgfiles)
    tpkg.install(['a=.9'], PASSPHRASE)
    # Now request 'a' and verify that we get back the currently installed
    # 'a' pkg rather than the newer one that is available from our test
    # packages
    requirements = []
    packages = {}
    tpkg.requirements_for_currently_installed_packages(requirements, packages)
    requirements << {:name => 'a', :type => :tpkg}
    solution_packages = tpkg.best_solution(requirements, packages, ['a'])
    assert_equal(1, solution_packages.length)
    assert_equal(:currently_installed, solution_packages.first[:source])
    assert_equal('a', solution_packages.first[:metadata][:name])
    assert_equal('.9', solution_packages.first[:metadata][:version])
    FileUtils.rm_f(older_apkg)
    FileUtils.rm_rf(testbase)

    # Test that we don't prefer installed packages if :prefer is false
    testbase = Tempdir.new("testbase")
    #  First install an older version of d
    older_dpkg = make_package(:output_directory => @tempoutdir, :change => { 'name' => 'd', 'version' => '.9' }, :remove => ['operatingsystem', 'architecture', 'posix_acl', 'windows_acl'])
    tpkg = Tpkg.new(:base => testbase, :sources => [older_dpkg] + @pkgfiles)
    tpkg.install(['d=.9'], PASSPHRASE)
    # Now request an update of 'd' and verify that we get back the newer
    # available 'd' pkg rather than the currently installed package.
    requirements = []
    packages = {}
    tpkg.requirements_for_currently_installed_packages(requirements, packages)
    # Remove preference for currently installed package
    packages['d'].each do |pkg|
      if pkg[:source] == :currently_installed
        pkg[:prefer] = false
      end
    end
    solution_packages = tpkg.best_solution(requirements, packages, ['d'])
    assert_equal(1, solution_packages.length)
    assert(solution_packages.first[:source].include?('d-1.3-1.tpkg'))
    FileUtils.rm_f(older_dpkg)
    FileUtils.rm_rf(testbase)

    # Test that we don't prefer installed packages if :prefer is false
    # This is a more complex test than the previous, as the 'a' package
    # in our test @pkgfiles has dependencies, whereas the initial older
    # version we install does not.  The new dependencies could throw off
    # the scoring process.
    testbase = Tempdir.new("testbase")
    #  First install an older version of a
    older_apkg = make_package(:output_directory => @tempoutdir, :change => { 'name' => 'a', 'version' => '.9' }, :remove => ['operatingsystem', 'architecture', 'posix_acl', 'windows_acl'])
    tpkg = Tpkg.new(:base => testbase, :sources => [older_apkg] + @pkgfiles)
    tpkg.install(['a=.9'], PASSPHRASE)
    # Now request an update of 'a' and verify that we get back the newer
    # available 'a' pkg rather than the currently installed package.
    requirements = []
    packages = {}
    tpkg.requirements_for_currently_installed_packages(requirements, packages)
    # Remove preference for currently installed package
    packages['a'].each do |pkg|
      if pkg[:source] == :currently_installed
        pkg[:prefer] = false
      end
    end
    solution_packages = tpkg.best_solution(requirements, packages, ['a'])
    # The solution should pull in the newer 'a' and its dependencies
    assert_equal(4, solution_packages.length)
    selectedapkg = solution_packages.find{|pkg| pkg[:metadata][:name] == 'a'}
    assert(selectedapkg[:source].include?('a-1.0-1.tpkg'))
    FileUtils.rm_f(older_apkg)
    FileUtils.rm_rf(testbase)
    
    # Test that we can handle simultaneous dependency on a native package and
    # a tpkg with the same name. For this we need a native package that is
    # generally available on systems that developers are likely to use, I'm
    # going to use wget for now.
    nativedep = make_package(:output_directory => @tempoutdir, :change => { 'name' => 'nativedep' }, :dependencies => {'wget' => {'native' => true}}, :remove => ['operatingsystem', 'architecture', 'posix_acl', 'windows_acl'])
    tpkgdep = make_package(:output_directory => @tempoutdir, :change => { 'name' => 'tpkgdep' }, :dependencies => {'wget' => {}}, :remove => ['operatingsystem', 'architecture', 'posix_acl', 'windows_acl'])
    wget = make_package(:output_directory => @tempoutdir, :change => { 'name' => 'wget' }, :remove => ['operatingsystem', 'architecture', 'posix_acl', 'windows_acl'])
    parent = make_package(:output_directory => @tempoutdir, :change => { 'name' => 'parent' }, :dependencies => {'nativedep' => {}, 'tpkgdep' => {}}, :remove => ['operatingsystem', 'architecture', 'posix_acl', 'windows_acl'])
    testbase = Tempdir.new("testbase")
    mixeddeppkgs = [nativedep, tpkgdep, wget, parent]
    tpkg = Tpkg.new(:base => testbase, :sources => mixeddeppkgs)
    solution_packages = tpkg.best_solution([{:name => 'parent', :type => :tpkg}], {}, ['parent'])
    # The solution should include the four tpkgs plus a native wget
    assert_equal(5, solution_packages.length)
    assert(solution_packages.any? {|sp| sp[:metadata][:name] == 'wget' && (sp[:source] == :native_available || sp[:source] == :native_installed)})
    assert(solution_packages.any? {|sp| sp[:metadata][:name] == 'wget' && sp[:source].include?('wget-1.0-1.tpkg')})
    mixeddeppkgs.each do |mdp|
      FileUtils.rm_f(mdp)
    end
    FileUtils.rm_rf(testbase)
    
    # Test with no valid solution, ensure it fails
    testbase = Tempdir.new("testbase")
    tpkg = Tpkg.new(:base => testbase, :sources => @pkgfiles)
    solution_packages = nil
    assert_nothing_raised { solution_packages = tpkg.best_solution([{:name => 'a', :type => :tpkg}, {:name => 'c', :minimum_version => '1.3', :type => :tpkg}], {}, ['a', 'c']) }
    assert_nil(solution_packages)
    FileUtils.rm_rf(testbase)
    
    # The test recreates a set of circumstances that triggered a bug at one
    # point.  There are several versions of the requested package which depend
    # on a non-existent package.  This particular arrangement led to
    # attempting to reference a nil value as a pkg.
    testbase = Tempdir.new("testbase")
    baddep1 = make_package(:output_directory => @tempoutdir, :change => { 'name' => 'baddep', 'version' => '1' }, :remove => ['operatingsystem', 'architecture', 'posix_acl', 'windows_acl'])
    baddep2 = make_package(:output_directory => @tempoutdir, :change => { 'name' => 'baddep', 'version' => '2' }, :dependencies => {'bogus' => {}}, :remove => ['operatingsystem', 'architecture', 'posix_acl', 'windows_acl'])
    baddep3 = make_package(:output_directory => @tempoutdir, :change => { 'name' => 'baddep', 'version' => '2' }, :dependencies => {'bogus' => {}}, :remove => ['operatingsystem', 'architecture', 'posix_acl', 'windows_acl'])
    tpkg = Tpkg.new(:base => testbase, :sources => [baddep1, baddep2, baddep3])
    solution_packages = nil
    assert_nothing_raised { solution_packages = tpkg.best_solution([{:name => 'baddep', :type => :tpkg}], {}, ['baddep']) }
    assert_equal(1, solution_packages.length)
    assert(solution_packages.first[:source] == baddep1)
    FileUtils.rm_rf(testbase)
    
    # This test recreates another set of circumstances that triggered a bug. 
    # The format of the packages argument to resolve_dependencies changed and
    # the attempts to dup it in order to avoid messing up the state of callers
    # of resolve_dependencies were no longer effective.  Thus the state of
    # callers of resolve_dependencies was messed up and it would fail to find
    # valid solutions.
    testbase = Tempdir.new("testbase")
    baddep1 = make_package(:output_directory => @tempoutdir, :change => { 'name' => 'baddep', 'version' => '1' }, :dependencies => {'notbogus' => {}}, :remove => ['operatingsystem', 'architecture', 'posix_acl', 'windows_acl'])
    baddep2 = make_package(:output_directory => @tempoutdir, :change => { 'name' => 'baddep', 'version' => '2' }, :dependencies => {'notbogus' => {}, 'bogus' => {}}, :remove => ['operatingsystem', 'architecture', 'posix_acl', 'windows_acl'])
    notbogus = make_package(:output_directory => @tempoutdir, :change => { 'name' => 'notbogus', 'version' => '1' }, :remove => ['operatingsystem', 'architecture', 'posix_acl', 'windows_acl'])
    bogus = make_package(:output_directory => @tempoutdir, :change => { 'name' => 'bogus', 'version' => '1', 'operatingsystem' => 'bogusos' }, :remove => ['architecture', 'posix_acl', 'windows_acl'])
    tpkg = Tpkg.new(:base => testbase, :sources => [baddep1, baddep2, notbogus, bogus])
    solution_packages = nil
    assert_nothing_raised { solution_packages = tpkg.best_solution([{:name => 'baddep', :type => :tpkg}], {}, ['baddep']) }
    assert_equal(1, solution_packages.length)
    assert(solution_packages.first[:source] == baddep1)
    FileUtils.rm_rf(testbase)
  end
  
  # best_solution is a thin wrapper of this method, most of the testing
  # is in test_best_solution
  def test_resolve_dependencies
    testbase = Tempdir.new("testbase")
    tpkg = Tpkg.new(:base => testbase, :sources => @pkgfiles)
    
    result = tpkg.resolve_dependencies([{:name => 'a', :type => :tpkg}], {:tpkg => {}, :native => {}}, ['a'])
    assert(result.has_key?(:solution))
    solution = result[:solution]
    
    # We should end up with a-1.0, b-1.0 (the specific one, not the generic
    # one), c-1.2 and d-1.2
    assert_equal(4, solution.length)
    good = ['a-1.0-1.tpkg', "b-1.0-1-#{Metadata.clean_for_filename(Tpkg.get_os)}.tpkg", 'c-1.2-1.tpkg', 'd-1.2-1.tpkg']
    solution.each { |pkg| assert(good.any? { |g| pkg[:source].include?(g) }) }
    
    FileUtils.rm_rf(testbase)
  end
  
  # This method is only used by resolve_dependencies, so the testing
  # here is minimal.<commit_msg>Fix bug in the test case I just added.  Forgot to update the asserts to match the situation I actually came up with to trigger the bug.<commit_after>#
# Test tpkg's ability to resolve dependencies
#

require File.dirname(__FILE__) + '/tpkgtest'

class TpkgDependencyTests < Test::Unit::TestCase
  include TpkgTests
  
  def setup
    Tpkg::set_prompt(false)
    
    @tempoutdir = Tempdir.new("tempoutdir")  # temp dir that will automatically get deleted at end of test run
                                             # can be used for storing packages
    @pkgfiles = []
    # a depends on b, and c >= 1.1, <= 1.2
    @pkgfiles << make_package(:output_directory => @tempoutdir, :change => { 'name' => 'a' }, :remove => ['operatingsystem', 'architecture'], :dependencies => {'b' => {}, 'c' => {'minimum_version' => '1.1', 'maximum_version' => '1.2'}})
    # generic b for all OSs
    # These two b packages will end up with the same filename, so we
    # manually rename this one
    bpkg = make_package(:output_directory => @tempoutdir, :change => { 'name' => 'b' }, :remove => ['operatingsystem', 'architecture'])
    bpkgnew = bpkg + '.generic'
    File.rename(bpkg, bpkgnew)
    @pkgfiles << bpkgnew
    # b specific to this OS (should prefer this one)
    @pkgfiles << make_package(:output_directory => @tempoutdir, :change => { 'name' => 'b', 'operatingsystem' => Tpkg::get_os }, :remove => ['architecture'])
    # c 1.0 to 1.3, a's dep should result in c-1.2 getting picked
    @pkgfiles << make_package(:output_directory => @tempoutdir, :change => { 'name' => 'c', 'version' => '1.0' }, :remove => ['operatingsystem', 'architecture'], :dependencies => {'d' => {'minimum_version' => '1.0', 'maximum_version' => '1.0'}})
    @pkgfiles << make_package(:output_directory => @tempoutdir, :change => { 'name' => 'c', 'version' => '1.1' }, :remove => ['operatingsystem', 'architecture'], :dependencies => {'d' => {'minimum_version' => '1.1', 'maximum_version' => '1.1'}})
    @pkgfiles << make_package(:output_directory => @tempoutdir, :change => { 'name' => 'c', 'version' => '1.2' }, :remove => ['operatingsystem', 'architecture'], :dependencies => {'d' => {'minimum_version' => '1.2', 'maximum_version' => '1.2'}})
    @pkgfiles << make_package(:output_directory => @tempoutdir, :change => { 'name' => 'c', 'version' => '1.3' }, :remove => ['operatingsystem', 'architecture'], :dependencies => {'d' => {'minimum_version' => '1.3', 'maximum_version' => '1.3'}})
    # d 1.0 to 1.3, c's dep should result in d-1.2 getting picked
    @pkgfiles << make_package(:output_directory => @tempoutdir, :change => { 'name' => 'd', 'version' => '1.0' }, :remove => ['operatingsystem', 'architecture'])
    @pkgfiles << make_package(:output_directory => @tempoutdir, :change => { 'name' => 'd', 'version' => '1.1' }, :remove => ['operatingsystem', 'architecture'])
    @pkgfiles << make_package(:output_directory => @tempoutdir, :change => { 'name' => 'd', 'version' => '1.2' }, :remove => ['operatingsystem', 'architecture'])
    @pkgfiles << make_package(:output_directory => @tempoutdir, :change => { 'name' => 'd', 'version' => '1.3' }, :remove => ['operatingsystem', 'architecture'])
  end
  
  def test_package_meets_requirement
    #
    # Test version handling
    #
    
    pkgfile = make_package(:output_directory => @tempoutdir, :remove => ['operatingsystem', 'architecture'])
    metadata = Tpkg::metadata_from_package(pkgfile)
    pkg = { :metadata => metadata, :source => pkgfile }
    req = { :name => 'testpkg' }
    
    # Below minimum version w/o package version
    req[:minimum_version] = '2.0'
    req[:maximum_version] = '3.0'
    req.delete(:minimum_package_version)
    req.delete(:maximum_package_version)
    assert(!Tpkg::package_meets_requirement?(pkg, req))
    # Below minimum version w/ package version
    req[:minimum_package_version] = '1.0'
    req[:maximum_package_version] = '2.0'
    assert(!Tpkg::package_meets_requirement?(pkg, req))
    # At minimum version w/o package version
    req[:minimum_version] = '1.0'
    req[:maximum_version] = '2.0'
    req.delete(:minimum_package_version)
    req.delete(:maximum_package_version)
    assert(Tpkg::package_meets_requirement?(pkg, req))
    # At minimum version w/ package version
    req[:minimum_package_version] = '1.0'
    req[:maximum_package_version] = '2.0'
    assert(Tpkg::package_meets_requirement?(pkg, req))
    # In middle of range w/o package version
    req[:minimum_version] = '0.5'
    req[:maximum_version] = '2.0'
    req.delete(:minimum_package_version)
    req.delete(:maximum_package_version)
    assert(Tpkg::package_meets_requirement?(pkg, req))
    # In middle of range w/ package version
    req[:minimum_version] = '0.5'
    req[:maximum_version] = '2.0'
    req[:minimum_package_version] = '0.5'
    req[:maximum_package_version] = '2.0'
    assert(Tpkg::package_meets_requirement?(pkg, req))
    # At maximum version w/o package version
    req[:minimum_version] = '0.5'
    req[:maximum_version] = '1.0'
    req.delete(:minimum_package_version)
    req.delete(:maximum_package_version)
    assert(Tpkg::package_meets_requirement?(pkg, req))
    # At maximum version w/ package version
    req[:minimum_version] = '0.5'
    req[:maximum_version] = '1.0'
    req[:minimum_package_version] = '0.5'
    req[:maximum_package_version] = '1.0'
    assert(Tpkg::package_meets_requirement?(pkg, req))
    # Above maximum version w/o package version
    req[:minimum_version] = '0.1'
    req[:maximum_version] = '0.5'
    req.delete(:minimum_package_version)
    req.delete(:maximum_package_version)
    assert(!Tpkg::package_meets_requirement?(pkg, req))
    # Above minimum version w/ package version
    req[:minimum_package_version] = '1.0'
    req[:maximum_package_version] = '2.0'
    assert(!Tpkg::package_meets_requirement?(pkg, req))

    FileUtils.rm_f(pkgfile)

    # More complicated test for PS-375
    pkgfile = make_package(:output_directory => @tempoutdir, :change => {'version' => '2.3', 'package_version' => '2' }, :remove => ['operatingsystem', 'architecture'])
    metadata = Tpkg::metadata_from_package(pkgfile)
    pkg = { :metadata => metadata, :source => pkgfile }
    req = { :name => 'testpkg' }
    # version number is not equal to min or max version. So we don't care if min/max package version satisfied or not
    req[:minimum_version] = '0.5'
    req[:maximum_version] = '3.0'
    req[:minimum_package_version] = '3'
    req[:maximum_package_version] = '3'
    assert(Tpkg::package_meets_requirement?(pkg, req))
    req[:minimum_package_version] = '1'
    req[:maximum_package_version] = '1'
    assert(Tpkg::package_meets_requirement?(pkg, req))
    # version is same as maximum_version, so we have to look at maximum_package_version
    req[:minimum_version] = '0.5'
    req[:maximum_version] = '2.3'
    req[:minimum_package_version] = '1'
    req[:maximum_package_version] = '1'
    assert(!Tpkg::package_meets_requirement?(pkg, req))
    req[:minimum_package_version] = '3'
    req[:maximum_package_version] = '3'
    assert(Tpkg::package_meets_requirement?(pkg, req))
    # version is same as minimum_version, so we have to look at minimum_package_version
    req[:minimum_version] = '2.3'
    req[:maximum_version] = '3.0'
    req[:minimum_package_version] = '3'
    req[:maximum_package_version] = '5'
    assert(!Tpkg::package_meets_requirement?(pkg, req))
    req[:minimum_package_version] = '2'
    req[:maximum_package_version] = '3'
    assert(Tpkg::package_meets_requirement?(pkg, req))

    FileUtils.rm_f(pkgfile)
    
    #
    # Test architecture and operatingsystem handling
    #
    
    req = { :name => 'testpkg' }
    
    # Package with no OS specified
    pkgfile = make_package(:output_directory => @tempoutdir, :remove => ['operatingsystem'], :change => {'architecture' => Facter['hardwaremodel'].value})
    metadata = Tpkg::metadata_from_package(pkgfile)
    pkg = { :metadata => metadata, :source => pkgfile }
    assert(Tpkg::package_meets_requirement?(pkg, req))
    FileUtils.rm_f(pkgfile)
    
    # Package with one matching OS
    pkgfile = make_package(:output_directory => @tempoutdir, :change => {'operatingsystem' => Tpkg::get_os, 'architecture' => Facter['hardwaremodel'].value})
    metadata = Tpkg::metadata_from_package(pkgfile)
    pkg = { :metadata => metadata, :source => pkgfile }
    assert(Tpkg::package_meets_requirement?(pkg, req))
    FileUtils.rm_f(pkgfile)
    
    # Package with a matching OS in a list of OSs
    pkgfile = make_package(:output_directory => @tempoutdir, :change => {'operatingsystem' => "RedHat,CentOS,#{Tpkg::get_os},FreeBSD,Solaris", 'architecture' => Facter['hardwaremodel'].value})
    metadata = Tpkg::metadata_from_package(pkgfile)
    pkg = { :metadata => metadata, :source => pkgfile }
    assert(Tpkg::package_meets_requirement?(pkg, req))
    FileUtils.rm_f(pkgfile)
    
    # Package with one non-matching OS
    pkgfile = make_package(:output_directory => @tempoutdir, :change => {'operatingsystem' => 'bogus_os', 'architecture' => Facter['hardwaremodel'].value})
    metadata = Tpkg::metadata_from_package(pkgfile)
    pkg = { :metadata => metadata, :source => pkgfile }
    assert(!Tpkg::package_meets_requirement?(pkg, req))
    FileUtils.rm_f(pkgfile)
    
    # Package with a list of non-matching OSs
    pkgfile = make_package(:output_directory => @tempoutdir, :change => {'operatingsystem' => 'bogus_os1,bogus_os2', 'architecture' => Facter['hardwaremodel'].value})
    metadata = Tpkg::metadata_from_package(pkgfile)
    pkg = { :metadata => metadata, :source => pkgfile }
    assert(!Tpkg::package_meets_requirement?(pkg, req))
    FileUtils.rm_f(pkgfile)
    
    # Package with no architecture specified
    pkgfile = make_package(:output_directory => @tempoutdir, :remove => ['architecture'], :change => {'operatingsystem' => Tpkg::get_os })
    metadata = Tpkg::metadata_from_package(pkgfile)
    pkg = { :metadata => metadata, :source => pkgfile }
    assert(Tpkg::package_meets_requirement?(pkg, req))
    FileUtils.rm_f(pkgfile)
    
    # Package with one matching architecture
    pkgfile = make_package(:output_directory => @tempoutdir, :change => {'operatingsystem' => Tpkg::get_os, 'architecture' => Facter['hardwaremodel'].value})
    metadata = Tpkg::metadata_from_package(pkgfile)
    pkg = { :metadata => metadata, :source => pkgfile }
    assert(Tpkg::package_meets_requirement?(pkg, req))
    FileUtils.rm_f(pkgfile)
    
    # Package with a matching architecture in a list of architectures
    pkgfile = make_package(:output_directory => @tempoutdir, :change => {'operatingsystem' => Tpkg::get_os, 'architecture' => "i386,x86_64,#{Facter['hardwaremodel'].value},sparc,powerpc"})
    metadata = Tpkg::metadata_from_package(pkgfile)
    pkg = { :metadata => metadata, :source => pkgfile }
    assert(Tpkg::package_meets_requirement?(pkg, req))
    FileUtils.rm_f(pkgfile)
    
    # Package with one non-matching architecture
    pkgfile = make_package(:output_directory => @tempoutdir, :change => {'operatingsystem' => Tpkg::get_os, 'architecture' => 'bogus_arch'})
    metadata = Tpkg::metadata_from_package(pkgfile)
    pkg = { :metadata => metadata, :source => pkgfile }
    assert(!Tpkg::package_meets_requirement?(pkg, req))
    FileUtils.rm_f(pkgfile)
    
    # Package with a list of non-matching architectures
    pkgfile = make_package(:output_directory => @tempoutdir, :change => {'operatingsystem' => Tpkg::get_os, 'architecture' => 'bogus_arch1,bogus_arch2'})
    metadata = Tpkg::metadata_from_package(pkgfile)
    pkg = { :metadata => metadata, :source => pkgfile }
    assert(!Tpkg::package_meets_requirement?(pkg, req))
    FileUtils.rm_f(pkgfile)

    # Package with operatingsystem and arch specified as regex
    pkgfile = make_package(:output_directory => @tempoutdir, :change => {'operatingsystem' => 'RedHat|CentOS|Fedora|Debian|Ubuntu|Solaris|FreeBSD|Darwin',  'architecture' => "i386|x86_64|#{Facter['hardwaremodel'].value}|sparc|powerpc"})
    metadata = Tpkg::metadata_from_package(pkgfile)
    pkg = { :metadata => metadata, :source => pkgfile }
    assert(Tpkg::package_meets_requirement?(pkg, req))
  end
  
  def test_available_packages_that_meet_requirement
    pkgfiles = []
    ['1.0', '1.1', '1.2', '1.3'].each do |ver|
      pkgfiles << make_package(:output_directory => @tempoutdir, :change => {'version' => ver}, :remove => ['operatingsystem', 'architecture'])
    end
    
    testbase = Tempdir.new("testbase")
    tpkg = Tpkg.new(:base => testbase, :sources => pkgfiles)
    
    pkgs = tpkg.available_packages_that_meet_requirement
    nonnativepkgs = pkgs.select do |pkg|
      pkg[:source] != :native_installed && pkg[:source] != :native_available
    end
    assert_equal(4, nonnativepkgs.length)
    
    req = { :name => 'testpkg' }
    
    req[:minimum_version] = '1.2'
    req[:maximum_version] = '2.0'
    pkgs = tpkg.available_packages_that_meet_requirement(req)
    assert_equal(2, pkgs.length)
    pkgs.each do |pkg|
      assert(pkg[:metadata][:version].to_f >= 1.2)
    end
    
    req[:minimum_version] = '1.1'
    req[:maximum_version] = '1.2'
    pkgs = tpkg.available_packages_that_meet_requirement(req)
    assert_equal(2, pkgs.length)
    pkgs.each do |pkg|
      assert(pkg[:metadata][:version].to_f >= 1.1)
      assert(pkg[:metadata][:version].to_f <= 1.2)
    end
    
    # Test a package name which has no available packages
    req[:name] = 'otherpkg'
    pkgs = tpkg.available_packages_that_meet_requirement(req)
    assert(pkgs.empty?)

    # PS-478
    pkgfiles << make_package(:output_directory => @tempoutdir, :change => {'version' => '2'}, :remove => ['operatingsystem', 'architecture', 'package_version'])
    pkgfiles << make_package(:output_directory => @tempoutdir, :change => {'version' => '2', 'package_version' => '1'}, :remove => ['operatingsystem', 'architecture'])
    pkgfiles << make_package(:output_directory => @tempoutdir, :change => {'version' => '2', 'package_version' => '112'}, :remove => ['operatingsystem', 'architecture'])
    tpkg = Tpkg.new(:base => testbase, :sources => pkgfiles)

    req = { :name => 'testpkg' }
    
    # Should only match package of version 2 and NO package version
    req[:allowed_versions] = '2'
    pkgs = tpkg.available_packages_that_meet_requirement(req)
    assert_equal(1, pkgs.length)

    # Should match any packages that has a version number that starts with 2
    req[:allowed_versions] = '2*'
    pkgs = tpkg.available_packages_that_meet_requirement(req)
    assert_equal(3, pkgs.length)

    # Should match any packages that is version 2 AND has a package version number
    req[:allowed_versions] = '2-*'
    pkgs = tpkg.available_packages_that_meet_requirement(req)
    assert_equal(2, pkgs.length)

    # Should match any packages that is version 2 AND has a package version number that ends with 2
    req[:allowed_versions] = '2-*2'
    pkgs = tpkg.available_packages_that_meet_requirement(req)
    assert_equal(1, pkgs.length)

    
    pkgfiles.each { |pkgfile| FileUtils.rm_f(pkgfile) }
    FileUtils.rm_rf(testbase)
  end
  
  def test_best_solution
    # Test that best_solution gives us the right answer using our test
    # package set in a new, clean base
    testbase = Tempdir.new("testbase")
    tpkg = Tpkg.new(:base => testbase, :sources => @pkgfiles)
    solution_packages = tpkg.best_solution([{:name => 'a', :type => :tpkg}], {}, ['a'])
    # We should end up with a-1.0, b-1.0 (the specific one, not the generic
    # one), c-1.2 and d-1.2
    assert_equal(4, solution_packages.length)
    good = ['a-1.0-1.tpkg', "b-1.0-1-#{Metadata.clean_for_filename(Tpkg.get_os)}.tpkg", 'c-1.2-1.tpkg', 'd-1.2-1.tpkg']
    solution_packages.each { |pkg| assert(good.any? { |g| pkg[:source].include?(g) }) }
    FileUtils.rm_rf(testbase)
    
    # Now run a test to verify that we prefer already installed packages
    testbase = Tempdir.new("testbase")
    #  First install an older version of a
    older_apkg = make_package(:output_directory => @tempoutdir, :change => { 'name' => 'a', 'version' => '.9' }, :remove => ['operatingsystem', 'architecture', 'posix_acl', 'windows_acl'])
    tpkg = Tpkg.new(:base => testbase, :sources => [older_apkg] + @pkgfiles)
    tpkg.install(['a=.9'], PASSPHRASE)
    # Now request 'a' and verify that we get back the currently installed
    # 'a' pkg rather than the newer one that is available from our test
    # packages
    requirements = []
    packages = {}
    tpkg.requirements_for_currently_installed_packages(requirements, packages)
    requirements << {:name => 'a', :type => :tpkg}
    solution_packages = tpkg.best_solution(requirements, packages, ['a'])
    assert_equal(1, solution_packages.length)
    assert_equal(:currently_installed, solution_packages.first[:source])
    assert_equal('a', solution_packages.first[:metadata][:name])
    assert_equal('.9', solution_packages.first[:metadata][:version])
    FileUtils.rm_f(older_apkg)
    FileUtils.rm_rf(testbase)

    # Test that we don't prefer installed packages if :prefer is false
    testbase = Tempdir.new("testbase")
    #  First install an older version of d
    older_dpkg = make_package(:output_directory => @tempoutdir, :change => { 'name' => 'd', 'version' => '.9' }, :remove => ['operatingsystem', 'architecture', 'posix_acl', 'windows_acl'])
    tpkg = Tpkg.new(:base => testbase, :sources => [older_dpkg] + @pkgfiles)
    tpkg.install(['d=.9'], PASSPHRASE)
    # Now request an update of 'd' and verify that we get back the newer
    # available 'd' pkg rather than the currently installed package.
    requirements = []
    packages = {}
    tpkg.requirements_for_currently_installed_packages(requirements, packages)
    # Remove preference for currently installed package
    packages['d'].each do |pkg|
      if pkg[:source] == :currently_installed
        pkg[:prefer] = false
      end
    end
    solution_packages = tpkg.best_solution(requirements, packages, ['d'])
    assert_equal(1, solution_packages.length)
    assert(solution_packages.first[:source].include?('d-1.3-1.tpkg'))
    FileUtils.rm_f(older_dpkg)
    FileUtils.rm_rf(testbase)

    # Test that we don't prefer installed packages if :prefer is false
    # This is a more complex test than the previous, as the 'a' package
    # in our test @pkgfiles has dependencies, whereas the initial older
    # version we install does not.  The new dependencies could throw off
    # the scoring process.
    testbase = Tempdir.new("testbase")
    #  First install an older version of a
    older_apkg = make_package(:output_directory => @tempoutdir, :change => { 'name' => 'a', 'version' => '.9' }, :remove => ['operatingsystem', 'architecture', 'posix_acl', 'windows_acl'])
    tpkg = Tpkg.new(:base => testbase, :sources => [older_apkg] + @pkgfiles)
    tpkg.install(['a=.9'], PASSPHRASE)
    # Now request an update of 'a' and verify that we get back the newer
    # available 'a' pkg rather than the currently installed package.
    requirements = []
    packages = {}
    tpkg.requirements_for_currently_installed_packages(requirements, packages)
    # Remove preference for currently installed package
    packages['a'].each do |pkg|
      if pkg[:source] == :currently_installed
        pkg[:prefer] = false
      end
    end
    solution_packages = tpkg.best_solution(requirements, packages, ['a'])
    # The solution should pull in the newer 'a' and its dependencies
    assert_equal(4, solution_packages.length)
    selectedapkg = solution_packages.find{|pkg| pkg[:metadata][:name] == 'a'}
    assert(selectedapkg[:source].include?('a-1.0-1.tpkg'))
    FileUtils.rm_f(older_apkg)
    FileUtils.rm_rf(testbase)
    
    # Test that we can handle simultaneous dependency on a native package and
    # a tpkg with the same name. For this we need a native package that is
    # generally available on systems that developers are likely to use, I'm
    # going to use wget for now.
    nativedep = make_package(:output_directory => @tempoutdir, :change => { 'name' => 'nativedep' }, :dependencies => {'wget' => {'native' => true}}, :remove => ['operatingsystem', 'architecture', 'posix_acl', 'windows_acl'])
    tpkgdep = make_package(:output_directory => @tempoutdir, :change => { 'name' => 'tpkgdep' }, :dependencies => {'wget' => {}}, :remove => ['operatingsystem', 'architecture', 'posix_acl', 'windows_acl'])
    wget = make_package(:output_directory => @tempoutdir, :change => { 'name' => 'wget' }, :remove => ['operatingsystem', 'architecture', 'posix_acl', 'windows_acl'])
    parent = make_package(:output_directory => @tempoutdir, :change => { 'name' => 'parent' }, :dependencies => {'nativedep' => {}, 'tpkgdep' => {}}, :remove => ['operatingsystem', 'architecture', 'posix_acl', 'windows_acl'])
    testbase = Tempdir.new("testbase")
    mixeddeppkgs = [nativedep, tpkgdep, wget, parent]
    tpkg = Tpkg.new(:base => testbase, :sources => mixeddeppkgs)
    solution_packages = tpkg.best_solution([{:name => 'parent', :type => :tpkg}], {}, ['parent'])
    # The solution should include the four tpkgs plus a native wget
    assert_equal(5, solution_packages.length)
    assert(solution_packages.any? {|sp| sp[:metadata][:name] == 'wget' && (sp[:source] == :native_available || sp[:source] == :native_installed)})
    assert(solution_packages.any? {|sp| sp[:metadata][:name] == 'wget' && sp[:source].include?('wget-1.0-1.tpkg')})
    mixeddeppkgs.each do |mdp|
      FileUtils.rm_f(mdp)
    end
    FileUtils.rm_rf(testbase)
    
    # Test with no valid solution, ensure it fails
    testbase = Tempdir.new("testbase")
    tpkg = Tpkg.new(:base => testbase, :sources => @pkgfiles)
    solution_packages = nil
    assert_nothing_raised { solution_packages = tpkg.best_solution([{:name => 'a', :type => :tpkg}, {:name => 'c', :minimum_version => '1.3', :type => :tpkg}], {}, ['a', 'c']) }
    assert_nil(solution_packages)
    FileUtils.rm_rf(testbase)
    
    # The test recreates a set of circumstances that triggered a bug at one
    # point.  There are several versions of the requested package which depend
    # on a non-existent package.  This particular arrangement led to
    # attempting to reference a nil value as a pkg.
    testbase = Tempdir.new("testbase")
    baddep1 = make_package(:output_directory => @tempoutdir, :change => { 'name' => 'baddep', 'version' => '1' }, :remove => ['operatingsystem', 'architecture', 'posix_acl', 'windows_acl'])
    baddep2 = make_package(:output_directory => @tempoutdir, :change => { 'name' => 'baddep', 'version' => '2' }, :dependencies => {'bogus' => {}}, :remove => ['operatingsystem', 'architecture', 'posix_acl', 'windows_acl'])
    baddep3 = make_package(:output_directory => @tempoutdir, :change => { 'name' => 'baddep', 'version' => '2' }, :dependencies => {'bogus' => {}}, :remove => ['operatingsystem', 'architecture', 'posix_acl', 'windows_acl'])
    tpkg = Tpkg.new(:base => testbase, :sources => [baddep1, baddep2, baddep3])
    solution_packages = nil
    assert_nothing_raised { solution_packages = tpkg.best_solution([{:name => 'baddep', :type => :tpkg}], {}, ['baddep']) }
    assert_equal(1, solution_packages.length)
    assert(solution_packages.first[:source] == baddep1)
    FileUtils.rm_rf(testbase)
    
    # This test recreates another set of circumstances that triggered a bug. 
    # The format of the packages argument to resolve_dependencies changed and
    # the attempts to dup it in order to avoid messing up the state of callers
    # of resolve_dependencies were no longer effective.  Thus the state of
    # callers of resolve_dependencies was messed up and it would fail to find
    # valid solutions.
    testbase = Tempdir.new("testbase")
    baddep1 = make_package(:output_directory => @tempoutdir, :change => { 'name' => 'baddep', 'version' => '1' }, :dependencies => {'notbogus' => {}}, :remove => ['operatingsystem', 'architecture', 'posix_acl', 'windows_acl'])
    baddep2 = make_package(:output_directory => @tempoutdir, :change => { 'name' => 'baddep', 'version' => '2' }, :dependencies => {'notbogus' => {}, 'bogus' => {}}, :remove => ['operatingsystem', 'architecture', 'posix_acl', 'windows_acl'])
    notbogus = make_package(:output_directory => @tempoutdir, :change => { 'name' => 'notbogus', 'version' => '1' }, :remove => ['operatingsystem', 'architecture', 'posix_acl', 'windows_acl'])
    bogus = make_package(:output_directory => @tempoutdir, :change => { 'name' => 'bogus', 'version' => '1', 'operatingsystem' => 'bogusos' }, :remove => ['architecture', 'posix_acl', 'windows_acl'])
    tpkg = Tpkg.new(:base => testbase, :sources => [baddep1, baddep2, notbogus, bogus])
    solution_packages = nil
    assert_nothing_raised { solution_packages = tpkg.best_solution([{:name => 'baddep', :type => :tpkg}], {}, ['baddep']) }
    assert_equal(2, solution_packages.length)
    assert(solution_packages.any? {|sp| sp[:source] == baddep1})
    assert(solution_packages.any? {|sp| sp[:source] == notbogus})
    FileUtils.rm_rf(testbase)
  end
  
  # best_solution is a thin wrapper of this method, most of the testing
  # is in test_best_solution
  def test_resolve_dependencies
    testbase = Tempdir.new("testbase")
    tpkg = Tpkg.new(:base => testbase, :sources => @pkgfiles)
    
    result = tpkg.resolve_dependencies([{:name => 'a', :type => :tpkg}], {:tpkg => {}, :native => {}}, ['a'])
    assert(result.has_key?(:solution))
    solution = result[:solution]
    
    # We should end up with a-1.0, b-1.0 (the specific one, not the generic
    # one), c-1.2 and d-1.2
    assert_equal(4, solution.length)
    good = ['a-1.0-1.tpkg', "b-1.0-1-#{Metadata.clean_for_filename(Tpkg.get_os)}.tpkg", 'c-1.2-1.tpkg', 'd-1.2-1.tpkg']
    solution.each { |pkg| assert(good.any? { |g| pkg[:source].include?(g) }) }
    
    FileUtils.rm_rf(testbase)
  end
  
  # This method is only used by resolve_dependencies, so the testing
  # here is minimal.
{'fraction_non_alphanumeric': 0.1548284735793201, 'fraction_numerical': 0.010912679217537682, 'mean_word_length': 4.3030987071619125, 'pattern_counts': {'":': 0, '<': 73, '<?xml version=': 0, '>': 607, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 4, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1053066', 'n_tokens_mistral': 17315, 'n_tokens_neox': 16758, 'n_words': 3893}