
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.
464 lines
12 KiB
Ruby
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
|