Files
brew/Library/Homebrew/test/cask/artifact/binary_spec.rb
T
Mike McQuaid 0376eda65e Skip cask link when symlink already correct
- A plain `brew upgrade` raised "It seems there is already a
  Binary at ..." even when the existing symlink resolved to the
  exact source about to be linked, because the realpath check sat
  behind `force`/`adopt`. The revert left the symlink in place so
  every retry failed identically until manual intervention.
- Treat an already-correct symlink as a no-op: log and skip the
  link. Genuine conflicts, such as a real file at the target or a
  symlink elsewhere, still require `--force` or `--adopt`.
- Rescue errors resolving the target, like `conflicting_formula`
  does, so unreadable symlinks fall back to the existing conflict
  and error handling instead of raising `Errno` exceptions.
- Fixes Homebrew/brew#23426.
2026-08-04 13:04:55 +01:00

140 lines
4.0 KiB
Ruby

# typed: false
# frozen_string_literal: true
RSpec.describe Cask::Artifact::Binary, :cask do
let(:cask) do
Cask::CaskLoader.load(cask_path("with-binary")).tap do |cask|
InstallHelper.install_without_artifacts(cask)
end
end
let(:artifacts) { cask.artifacts.grep(described_class) }
let(:binarydir) { cask.config.binarydir }
let(:expected_path) { binarydir.join("binary") }
around do |example|
binarydir.mkpath
example.run
ensure
FileUtils.rm_f expected_path
FileUtils.rmdir binarydir
end
context "when --no-binaries is specified" do
let(:cask) do
Cask::CaskLoader.load(cask_path("with-binary"))
end
it "doesn't link the binary when --no-binaries is specified" do
Cask::Installer.new(cask, binaries: false).install
expect(expected_path).not_to exist
end
end
it "links the binary to the proper directory" do
artifacts.each do |artifact|
artifact.install_phase(command: NeverSudoSystemCommand, force: false)
end
expect(expected_path).to be_a_symlink
expect(expected_path.readlink).to exist
end
context "when the binary is not executable" do
let(:cask) do
Cask::CaskLoader.load(cask_path("with-non-executable-binary")).tap do |cask|
InstallHelper.install_without_artifacts(cask)
end
end
let(:expected_path) { cask.config.binarydir.join("naked_non_executable") }
it "makes the binary executable" do
expect(FileUtils).to receive(:chmod)
.with("+x", cask.staged_path.join("naked_non_executable")).and_call_original
artifacts.each do |artifact|
artifact.install_phase(command: NeverSudoSystemCommand, force: false)
end
expect(expected_path).to be_a_symlink
expect(expected_path.readlink).to be_executable
end
end
it "avoids clobbering an existing binary by linking over it" do
FileUtils.touch expected_path
expect do
artifacts.each do |artifact|
artifact.install_phase(command: NeverSudoSystemCommand, force: false)
end
end.to raise_error(Cask::CaskError)
expect(expected_path).not_to be :symlink?
end
it "avoids clobbering an existing symlink" do
expected_path.make_symlink("/tmp")
expect do
artifacts.each do |artifact|
artifact.install_phase(command: NeverSudoSystemCommand, force: false)
end
end.to raise_error(Cask::CaskError)
expect(File.readlink(expected_path)).to eq("/tmp")
end
it "skips linking when the target is already a symlink to the source" do
artifact = artifacts.first
expected_path.make_symlink(artifact.source)
expect do
artifact.install_phase(command: NeverSudoSystemCommand, force: false)
end.to output(/is already linked/).to_stdout
expect(expected_path.readlink).to eq(artifact.source)
end
it "raises a clean error when the target symlink cannot be resolved" do
artifact = artifacts.first
expected_path.make_symlink(artifact.source)
allow(artifact.target).to receive(:realpath).and_raise(Errno::EACCES)
expect do
artifact.install_phase(command: NeverSudoSystemCommand, force: false)
end.to raise_error(Cask::CaskError, /already a Binary/)
end
it "creates parent directory if it doesn't exist" do
FileUtils.rmdir binarydir
artifacts.each do |artifact|
artifact.install_phase(command: NeverSudoSystemCommand, force: false)
end
expect(expected_path.exist?).to be true
end
context "when the binary is inside an app package" do
let(:cask) do
Cask::CaskLoader.load(cask_path("with-embedded-binary")).tap do |cask|
InstallHelper.install_without_artifacts(cask)
end
end
it "links the binary to the proper directory" do
cask.artifacts.grep(Cask::Artifact::App).each do |artifact|
artifact.install_phase(command: NeverSudoSystemCommand, force: false)
end
artifacts.each do |artifact|
artifact.install_phase(command: NeverSudoSystemCommand, force: false)
end
expect(expected_path).to be_a_symlink
expect(expected_path.readlink).to exist
end
end
end