brew/Library/Homebrew/test/keg_test.rb
Alyssa Ross 8b30abe060 keg: handle dependencies of moved/renamed formulae
In #1497 I switched from Keg#to_formula for comparing kegs to formulae
to comparing the name and tap in the keg's tab to the name and tap of
the formula.

However, this fails to match if the name and tap of the formula have
changed since the keg was installed, so it's clearly better to use
Keg#to_formula where possible, and fall back to the information in the
tab when #to_formula can't be used.
2016-12-31 17:53:06 +00:00

464 lines
12 KiB
Ruby

require "testing_env"
require "keg"
require "stringio"
class LinkTestCase < Homebrew::TestCase
include FileUtils
def setup_test_keg(name, version)
path = HOMEBREW_CELLAR.join(name, version)
path.join("bin").mkpath
%w[hiworld helloworld goodbye_cruel_world].each do |file|
touch path.join("bin", file)
end
keg = Keg.new(path)
@kegs ||= []
@kegs << keg
keg
end
def setup
@keg = setup_test_keg("foo", "1.0")
@dst = HOMEBREW_PREFIX.join("bin", "helloworld")
@nonexistent = Pathname.new("/some/nonexistent/path")
@mode = OpenStruct.new
@old_stdout = $stdout
$stdout = StringIO.new
mkpath HOMEBREW_PREFIX/"bin"
mkpath HOMEBREW_PREFIX/"lib"
end
def teardown
@kegs.each do |keg|
keg.unlink
keg.uninstall
end
$stdout = @old_stdout
rmtree HOMEBREW_PREFIX/"bin"
rmtree HOMEBREW_PREFIX/"lib"
end
end
class LinkTests < LinkTestCase
def test_all
Formula.clear_racks_cache
assert_equal [@keg], Keg.all
end
def test_empty_installation
%w[.DS_Store INSTALL_RECEIPT.json LICENSE.txt].each do |file|
touch @keg/file
end
assert_predicate @keg, :exist?
assert_predicate @keg, :directory?
refute_predicate @keg, :empty_installation?
(@keg/"bin").rmtree
assert_predicate @keg, :empty_installation?
end
def test_linking_keg
assert_equal 3, @keg.link
(HOMEBREW_PREFIX/"bin").children.each { |c| assert_predicate c.readlink, :relative? }
end
def test_unlinking_keg
@keg.link
assert_predicate @dst, :symlink?
assert_equal 3, @keg.unlink
refute_predicate @dst, :symlink?
end
def test_oldname_opt_record
assert_nil @keg.oldname_opt_record
oldname_opt_record = HOMEBREW_PREFIX/"opt/oldfoo"
oldname_opt_record.make_relative_symlink(HOMEBREW_CELLAR/"foo/1.0")
assert_equal oldname_opt_record, @keg.oldname_opt_record
end
def test_optlink_relink
oldname_opt_record = HOMEBREW_PREFIX/"opt/oldfoo"
oldname_opt_record.make_relative_symlink(HOMEBREW_CELLAR/"foo/1.0")
keg_record = HOMEBREW_CELLAR.join("foo", "2.0")
keg_record.join("bin").mkpath
keg = Keg.new(keg_record)
keg.optlink
assert_equal keg_record, oldname_opt_record.resolved_path
keg.uninstall
refute_predicate oldname_opt_record, :symlink?
end
def test_remove_oldname_opt_record
oldname_opt_record = HOMEBREW_PREFIX/"opt/oldfoo"
oldname_opt_record.make_relative_symlink(HOMEBREW_CELLAR/"foo/2.0")
@keg.remove_oldname_opt_record
assert_predicate oldname_opt_record, :symlink?
oldname_opt_record.unlink
oldname_opt_record.make_relative_symlink(HOMEBREW_CELLAR/"foo/1.0")
@keg.remove_oldname_opt_record
refute_predicate oldname_opt_record, :symlink?
end
def test_link_dry_run
@mode.dry_run = true
assert_equal 0, @keg.link(@mode)
refute_predicate @keg, :linked?
["hiworld", "helloworld", "goodbye_cruel_world"].each do |file|
assert_match "#{HOMEBREW_PREFIX}/bin/#{file}", $stdout.string
end
assert_equal 3, $stdout.string.lines.count
end
def test_linking_fails_when_already_linked
@keg.link
assert_raises(Keg::AlreadyLinkedError) { @keg.link }
end
def test_linking_fails_when_files_exist
touch @dst
assert_raises(Keg::ConflictError) { @keg.link }
end
def test_link_ignores_broken_symlinks_at_target
src = @keg.join("bin", "helloworld")
@dst.make_symlink(@nonexistent)
@keg.link
assert_equal src.relative_path_from(@dst.dirname), @dst.readlink
end
def test_link_overwrite
touch @dst
@mode.overwrite = true
assert_equal 3, @keg.link(@mode)
assert_predicate @keg, :linked?
end
def test_link_overwrite_broken_symlinks
@dst.make_symlink "nowhere"
@mode.overwrite = true
assert_equal 3, @keg.link(@mode)
assert_predicate @keg, :linked?
end
def test_link_overwrite_dryrun
touch @dst
@mode.overwrite = true
@mode.dry_run = true
assert_equal 0, @keg.link(@mode)
refute_predicate @keg, :linked?
assert_equal "#{@dst}\n", $stdout.string
end
def test_unlink_prunes_empty_toplevel_directories
mkpath HOMEBREW_PREFIX/"lib/foo/bar"
mkpath @keg/"lib/foo/bar"
touch @keg/"lib/foo/bar/file1"
@keg.unlink
refute_predicate HOMEBREW_PREFIX/"lib/foo", :directory?
end
def test_unlink_ignores_ds_store_when_pruning_empty_dirs
mkpath HOMEBREW_PREFIX/"lib/foo/bar"
touch HOMEBREW_PREFIX/"lib/foo/.DS_Store"
mkpath @keg/"lib/foo/bar"
touch @keg/"lib/foo/bar/file1"
@keg.unlink
refute_predicate HOMEBREW_PREFIX/"lib/foo", :directory?
refute_predicate HOMEBREW_PREFIX/"lib/foo/.DS_Store", :exist?
end
def test_linking_creates_opt_link
refute_predicate @keg, :optlinked?
@keg.link
assert_predicate @keg, :optlinked?
end
def test_unlinking_does_not_remove_opt_link
@keg.link
@keg.unlink
assert_predicate @keg, :optlinked?
end
def test_existing_opt_link
@keg.opt_record.make_relative_symlink Pathname.new(@keg)
@keg.optlink
assert_predicate @keg, :optlinked?
end
def test_existing_opt_link_directory
@keg.opt_record.mkpath
@keg.optlink
assert_predicate @keg, :optlinked?
end
def test_existing_opt_link_file
@keg.opt_record.parent.mkpath
@keg.opt_record.write("foo")
@keg.optlink
assert_predicate @keg, :optlinked?
end
def test_linked_keg
refute_predicate @keg, :linked?
@keg.link
assert_predicate @keg, :linked?
@keg.unlink
refute_predicate @keg, :linked?
end
def test_unlink_preserves_broken_symlink_pointing_outside_the_keg
@keg.link
@dst.delete
@dst.make_symlink(@nonexistent)
@keg.unlink
assert_predicate @dst, :symlink?
end
def test_unlink_preserves_broken_symlink_pointing_into_the_keg
@keg.link
@dst.resolved_path.delete
@keg.unlink
assert_predicate @dst, :symlink?
end
def test_unlink_preserves_symlink_pointing_outside_of_keg
@keg.link
@dst.delete
@dst.make_symlink(Pathname.new("/bin/sh"))
@keg.unlink
assert_predicate @dst, :symlink?
end
def test_unlink_preserves_real_file
@keg.link
@dst.delete
touch @dst
@keg.unlink
assert_predicate @dst, :file?
end
def test_unlink_ignores_nonexistent_file
@keg.link
@dst.delete
assert_equal 2, @keg.unlink
end
def test_pkgconfig_is_mkpathed
link = HOMEBREW_PREFIX.join("lib", "pkgconfig")
@keg.join("lib", "pkgconfig").mkpath
@keg.link
assert_predicate link.lstat, :directory?
end
def test_cmake_is_mkpathed
link = HOMEBREW_PREFIX.join("lib", "cmake")
@keg.join("lib", "cmake").mkpath
@keg.link
assert_predicate link.lstat, :directory?
end
def test_symlinks_are_linked_directly
link = HOMEBREW_PREFIX.join("lib", "pkgconfig")
@keg.join("lib", "example").mkpath
@keg.join("lib", "pkgconfig").make_symlink "example"
@keg.link
assert_predicate link.resolved_path, :symlink?
assert_predicate link.lstat, :symlink?
end
def test_links_to_symlinks_are_not_removed
a = HOMEBREW_CELLAR.join("a", "1.0")
b = HOMEBREW_CELLAR.join("b", "1.0")
a.join("lib", "example").mkpath
a.join("lib", "example2").make_symlink "example"
b.join("lib", "example2").mkpath
a = Keg.new(a)
b = Keg.new(b)
a.link
lib = HOMEBREW_PREFIX.join("lib")
assert_equal 2, lib.children.length
assert_raises(Keg::ConflictError) { b.link }
assert_equal 2, lib.children.length
ensure
a.unlink
a.uninstall
b.uninstall
end
def test_removes_broken_symlinks_that_conflict_with_directories
a = HOMEBREW_CELLAR.join("a", "1.0")
a.join("lib", "foo").mkpath
keg = Keg.new(a)
link = HOMEBREW_PREFIX.join("lib", "foo")
link.parent.mkpath
link.make_symlink(@nonexistent)
keg.link
ensure
keg.unlink
keg.uninstall
end
end
class InstalledDependantsTests < LinkTestCase
def stub_formula_name(name)
f = formula(name) { url "foo-1.0" }
stub_formula_loader f
stub_formula_loader f, "homebrew/core/#{f}"
f
end
def setup_test_keg(name, version)
f = stub_formula_name(name)
keg = super
Tab.create(f, DevelopmentTools.default_compiler, :libcxx).write
keg
end
def setup
super
@dependent = setup_test_keg("bar", "1.0")
end
def alter_tab(keg = @dependent)
tab = Tab.for_keg(keg)
yield tab
tab.write
end
# 1.1.6 is the earliest version of Homebrew that generates correct runtime
# dependency lists in tabs.
def dependencies(deps, homebrew_version: "1.1.6")
alter_tab do |tab|
tab.homebrew_version = homebrew_version
tab.tabfile = @dependent.join("INSTALL_RECEIPT.json")
tab.runtime_dependencies = deps
end
end
def unreliable_dependencies(deps)
# 1.1.5 is (hopefully!) the last version of Homebrew that generates
# incorrect runtime dependency lists in tabs.
dependencies(deps, homebrew_version: "1.1.5")
end
# Test with a keg whose formula isn't known.
# This can happen if e.g. a formula is installed
# from a file path or URL.
def test_unknown_formula
Formulary.unstub(:loader_for)
alter_tab(@keg) do |t|
t.source["tap"] = "some/tap"
t.source["path"] = nil
end
dependencies [{ "full_name" => "some/tap/foo", "version" => "1.0" }]
assert_equal [@dependent], @keg.installed_dependents
assert_equal [[@keg], ["bar 1.0"]], Keg.find_some_installed_dependents([@keg])
end
def test_a_dependency_with_no_tap_in_tab
@tap_dep = setup_test_keg("baz", "1.0")
alter_tab(@keg) { |t| t.source["tap"] = nil }
dependencies nil
Formula["bar"].class.depends_on "foo"
Formula["bar"].class.depends_on "baz"
result = Keg.find_some_installed_dependents([@keg, @tap_dep])
assert_equal [[@keg, @tap_dep], ["bar"]], result
end
def test_no_dependencies_anywhere
dependencies nil
assert_empty @keg.installed_dependents
assert_nil Keg.find_some_installed_dependents([@keg])
end
def test_missing_formula_dependency
dependencies nil
Formula["bar"].class.depends_on "foo"
assert_empty @keg.installed_dependents
assert_equal [[@keg], ["bar"]], Keg.find_some_installed_dependents([@keg])
end
def test_uninstalling_dependent_and_dependency
dependencies nil
Formula["bar"].class.depends_on "foo"
assert_empty @keg.installed_dependents
assert_nil Keg.find_some_installed_dependents([@keg, @dependent])
end
def test_renamed_dependency
dependencies nil
stub_formula_loader Formula["foo"], "homebrew/core/foo-old"
renamed_path = HOMEBREW_CELLAR/"foo-old"
(HOMEBREW_CELLAR/"foo").rename(renamed_path)
renamed_keg = Keg.new(renamed_path.join("1.0"))
Formula["bar"].class.depends_on "foo"
result = Keg.find_some_installed_dependents([renamed_keg])
assert_equal [[renamed_keg], ["bar"]], result
ensure
# Move it back to where it was so it'll be cleaned up.
(HOMEBREW_CELLAR/"foo-old").rename(HOMEBREW_CELLAR/"foo")
end
def test_empty_dependencies_in_tab
dependencies []
assert_empty @keg.installed_dependents
assert_nil Keg.find_some_installed_dependents([@keg])
end
def test_same_name_different_version_in_tab
dependencies [{ "full_name" => "foo", "version" => "1.1" }]
assert_empty @keg.installed_dependents
assert_nil Keg.find_some_installed_dependents([@keg])
end
def test_different_name_same_version_in_tab
stub_formula_name("baz")
dependencies [{ "full_name" => "baz", "version" => @keg.version.to_s }]
assert_empty @keg.installed_dependents
assert_nil Keg.find_some_installed_dependents([@keg])
end
def test_same_name_and_version_in_tab
dependencies [{ "full_name" => "foo", "version" => "1.0" }]
assert_equal [@dependent], @keg.installed_dependents
assert_equal [[@keg], ["bar 1.0"]], Keg.find_some_installed_dependents([@keg])
end
def test_fallback_for_old_versions
unreliable_dependencies [{ "full_name" => "baz", "version" => "1.0" }]
Formula["bar"].class.depends_on "foo"
assert_empty @keg.installed_dependents
assert_equal [[@keg], ["bar"]], Keg.find_some_installed_dependents([@keg])
end
end