395 lines
12 KiB
Ruby
Raw Normal View History

# frozen_string_literal: true
2018-09-06 08:29:14 +02:00
describe Cask::Artifact::App, :cask do
let(:cask) { Cask::CaskLoader.load(cask_path("local-caffeine")) }
2023-05-11 13:29:56 -05:00
let(:command) { NeverSudoSystemCommand }
let(:adopt) { false }
let(:force) { false }
2017-10-04 17:54:52 +02:00
let(:app) { cask.artifacts.find { |a| a.is_a?(described_class) } }
let(:source_path) { cask.staged_path.join("Caffeine.app") }
2019-02-02 17:11:37 +01:00
let(:target_path) { cask.config.appdir.join("Caffeine.app") }
let(:install_phase) { app.install_phase(command: command, adopt: adopt, force: force) }
let(:uninstall_phase) { app.uninstall_phase(command: command, force: force) }
before do
2017-02-08 14:23:18 +01:00
InstallHelper.install_without_artifacts(cask)
end
2016-08-18 22:11:42 +03:00
describe "install_phase" do
it "installs the given app using the proper target directory" do
2017-07-29 19:55:05 +02:00
install_phase
2016-08-18 22:11:42 +03:00
2017-02-08 14:23:18 +01:00
expect(target_path).to be_a_directory
expect(source_path).to be_a_symlink
2016-08-18 22:11:42 +03:00
end
describe "when app is in a subdirectory" do
let(:cask) do
2018-09-06 08:29:14 +02:00
Cask::Cask.new("subdir") do
url "file://#{TEST_FIXTURE_DIR}/cask/caffeine.zip"
homepage "https://brew.sh/local-caffeine"
version "1.2.3"
sha256 "67cdb8a02803ef37fdbf7e0be205863172e41a561ca446cd84f0d7ab35a99d94"
app "subdir/Caffeine.app"
end
end
2016-08-18 22:11:42 +03:00
it "installs the given app using the proper target directory" do
appsubdir = cask.staged_path.join("subdir").tap(&:mkpath)
FileUtils.mv(source_path, appsubdir)
2016-08-18 22:11:42 +03:00
2017-07-29 19:55:05 +02:00
install_phase
2016-08-18 22:11:42 +03:00
2017-02-08 14:23:18 +01:00
expect(target_path).to be_a_directory
expect(appsubdir.join("Caffeine.app")).to be_a_symlink
2016-08-18 22:11:42 +03:00
end
end
it "only uses apps when they are specified" do
staged_app_copy = source_path.sub("Caffeine.app", "Caffeine Deluxe.app")
FileUtils.cp_r source_path, staged_app_copy
2016-08-18 22:11:42 +03:00
2017-07-29 19:55:05 +02:00
install_phase
2016-08-18 22:11:42 +03:00
2017-02-08 14:23:18 +01:00
expect(target_path).to be_a_directory
expect(source_path).to be_a_symlink
2016-08-18 22:11:42 +03:00
2019-02-02 17:11:37 +01:00
expect(cask.config.appdir.join("Caffeine Deluxe.app")).not_to exist
2017-02-08 14:23:18 +01:00
expect(cask.staged_path.join("Caffeine Deluxe.app")).to exist
2016-08-18 22:11:42 +03:00
end
describe "when the target already exists" do
before do
2016-08-18 22:11:42 +03:00
target_path.mkpath
end
2016-08-18 22:11:42 +03:00
it "avoids clobbering an existing app" do
expect { install_phase }.to raise_error(
2018-09-06 08:29:14 +02:00
Cask::CaskError,
"It seems there is already an App at '#{target_path}'.",
)
2016-11-29 11:04:45 +01:00
2017-02-08 14:23:18 +01:00
expect(source_path).to be_a_directory
expect(target_path).to be_a_directory
expect(File.identical?(source_path, target_path)).to be false
2016-08-18 22:11:42 +03:00
contents_path = target_path.join("Contents/Info.plist")
2017-02-08 14:23:18 +01:00
expect(contents_path).not_to exist
2016-08-18 22:11:42 +03:00
end
describe "given the adopt option" do
let(:adopt) { true }
describe "when the target compares different from the source" do
it "avoids clobbering the existing app" do
stdout = <<~EOS
==> Adopting existing App at '#{target_path}'
EOS
expect { install_phase }
.to output(stdout).to_stdout
.and raise_error(
Cask::CaskError,
"It seems the existing App is different from the one being installed.",
)
expect(source_path).to be_a_directory
expect(target_path).to be_a_directory
expect(File.identical?(source_path, target_path)).to be false
contents_path = target_path.join("Contents/Info.plist")
expect(contents_path).not_to exist
end
end
describe "when the target compares the same as the source" do
before do
target_path.delete
FileUtils.cp_r source_path, target_path
end
it "adopts the existing app" do
stdout = <<~EOS
==> Adopting existing App at '#{target_path}'
EOS
stderr = ""
expect { install_phase }
.to output(stdout).to_stdout
.and output(stderr).to_stderr
expect(source_path).to be_a_symlink
expect(target_path).to be_a_directory
contents_path = target_path.join("Contents/Info.plist")
expect(contents_path).to exist
end
end
end
2016-08-18 22:11:42 +03:00
describe "given the force option" do
let(:force) { true }
2016-08-18 22:11:42 +03:00
before do
allow(User).to receive(:current).and_return(User.new("fake_user"))
2016-08-18 22:11:42 +03:00
end
describe "target is both writable and user-owned" do
it "overwrites the existing app" do
2017-10-15 02:28:32 +02:00
stdout = <<~EOS
2021-01-26 15:21:24 -05:00
==> Removing App '#{target_path}'
==> Moving App 'Caffeine.app' to '#{target_path}'
EOS
2016-08-18 22:11:42 +03:00
2017-10-15 02:28:32 +02:00
stderr = <<~EOS
2016-11-29 11:04:45 +01:00
Warning: It seems there is already an App at '#{target_path}'; overwriting.
EOS
2017-03-10 09:33:48 +01:00
expect { install_phase }
.to output(stdout).to_stdout
.and output(stderr).to_stderr
2016-11-29 11:04:45 +01:00
expect(source_path).to be_a_symlink
2017-02-08 14:23:18 +01:00
expect(target_path).to be_a_directory
2016-08-18 22:11:42 +03:00
contents_path = target_path.join("Contents/Info.plist")
2017-02-08 14:23:18 +01:00
expect(contents_path).to exist
2016-08-18 22:11:42 +03:00
end
end
describe "target is user-owned but contains read-only files" do
before do
FileUtils.touch "#{target_path}/foo"
FileUtils.chmod 0555, target_path
2016-08-18 22:11:42 +03:00
end
2018-09-20 09:07:56 +01:00
after do
FileUtils.chmod 0755, target_path
end
2016-08-18 22:11:42 +03:00
it "overwrites the existing app" do
expect(command).to receive(:run).with("/usr/bin/chflags",
args: ["-R", "--", "000", target_path]).and_call_original
expect(command).to receive(:run).with("/bin/chmod",
args: ["-R", "--", "u+rwx", target_path]).and_call_original
expect(command).to receive(:run).with("/bin/chmod",
args: ["-R", "-N", target_path]).and_call_original
2016-08-18 22:11:42 +03:00
2017-10-15 02:28:32 +02:00
stdout = <<~EOS
2021-01-26 15:21:24 -05:00
==> Removing App '#{target_path}'
==> Moving App 'Caffeine.app' to '#{target_path}'
EOS
2016-08-18 22:11:42 +03:00
2017-10-15 02:28:32 +02:00
stderr = <<~EOS
2016-11-29 11:04:45 +01:00
Warning: It seems there is already an App at '#{target_path}'; overwriting.
EOS
2017-03-10 09:33:48 +01:00
expect { install_phase }
.to output(stdout).to_stdout
.and output(stderr).to_stderr
2016-11-29 11:04:45 +01:00
expect(source_path).to be_a_symlink
2017-02-08 14:23:18 +01:00
expect(target_path).to be_a_directory
2016-08-18 22:11:42 +03:00
contents_path = target_path.join("Contents/Info.plist")
2017-02-08 14:23:18 +01:00
expect(contents_path).to exist
2016-08-18 22:11:42 +03:00
end
end
end
end
describe "when the target is a broken symlink" do
let(:deleted_path) { cask.staged_path.join("Deleted.app") }
2016-08-18 22:11:42 +03:00
before do
2016-08-18 22:11:42 +03:00
deleted_path.mkdir
File.symlink(deleted_path, target_path)
deleted_path.rmdir
end
it "leaves the target alone" do
expect { install_phase }.to raise_error(
2018-09-06 08:29:14 +02:00
Cask::CaskError, "It seems there is already an App at '#{target_path}'."
)
2017-02-08 14:23:18 +01:00
expect(target_path).to be_a_symlink
2016-08-18 22:11:42 +03:00
end
describe "given the force option" do
let(:force) { true }
2016-08-18 22:11:42 +03:00
it "overwrites the existing app" do
2017-10-15 02:28:32 +02:00
stdout = <<~EOS
2021-01-26 15:21:24 -05:00
==> Removing App '#{target_path}'
==> Moving App 'Caffeine.app' to '#{target_path}'
EOS
2016-08-18 22:11:42 +03:00
2017-10-15 02:28:32 +02:00
stderr = <<~EOS
2016-11-29 11:04:45 +01:00
Warning: It seems there is already an App at '#{target_path}'; overwriting.
EOS
2017-03-10 09:33:48 +01:00
expect { install_phase }
.to output(stdout).to_stdout
.and output(stderr).to_stderr
2016-11-29 11:04:45 +01:00
expect(source_path).to be_a_symlink
2017-02-08 14:23:18 +01:00
expect(target_path).to be_a_directory
2016-08-18 22:11:42 +03:00
contents_path = target_path.join("Contents/Info.plist")
2017-02-08 14:23:18 +01:00
expect(contents_path).to exist
2016-08-18 22:11:42 +03:00
end
end
end
it "gives a warning if the source doesn't exist" do
source_path.rmtree
2016-08-18 22:11:42 +03:00
2017-03-10 09:33:48 +01:00
message = "It seems the App source '#{source_path}' is not there."
2016-08-18 22:11:42 +03:00
2018-09-06 08:29:14 +02:00
expect { install_phase }.to raise_error(Cask::CaskError, message)
2016-08-18 22:11:42 +03:00
end
end
describe "uninstall_phase" do
after do
FileUtils.chmod 0755, target_path if target_path.exist?
FileUtils.chmod 0755, source_path if source_path.exist?
end
2017-02-08 14:23:18 +01:00
it "deletes managed apps" do
2017-07-29 19:55:05 +02:00
install_phase
2017-02-08 14:23:18 +01:00
expect(target_path).to exist
2016-08-18 22:11:42 +03:00
2017-07-29 19:55:05 +02:00
uninstall_phase
2016-08-18 22:11:42 +03:00
2017-02-08 14:23:18 +01:00
expect(target_path).not_to exist
2016-08-18 22:11:42 +03:00
end
it "backs up read-only managed apps" do
install_phase
FileUtils.chmod 0544, target_path
expect { uninstall_phase }.to raise_error(Errno::ENOTEMPTY)
expect(source_path).to be_a_directory
end
2016-08-18 22:11:42 +03:00
end
describe "summary" do
let(:description) { app.class.english_description }
let(:contents) { app.summarize_installed }
2016-08-18 22:11:42 +03:00
it "returns the correct english_description" do
2017-02-08 14:23:18 +01:00
expect(description).to eq("Apps")
2016-08-18 22:11:42 +03:00
end
describe "app is correctly installed" do
2017-02-08 14:23:18 +01:00
it "returns the path to the app" do
2017-07-29 19:55:05 +02:00
install_phase
2016-08-18 22:11:42 +03:00
expect(contents).to eq("#{target_path} (#{target_path.abv})")
2016-08-18 22:11:42 +03:00
end
end
describe "app is missing" do
it "returns a warning and the supposed path to the app" do
expect(contents).to match(/.*Missing App.*: #{target_path}/)
2016-08-18 22:11:42 +03:00
end
end
end
2023-04-04 10:21:44 -05:00
describe "upgrade" do
before do
2023-04-04 10:21:44 -05:00
install_phase
end
2023-04-04 10:21:44 -05:00
# Fix for https://github.com/Homebrew/homebrew-cask/issues/102721
it "reuses the same directory" do
2023-04-04 10:21:44 -05:00
contents_path = target_path.join("Contents/Info.plist")
expect(target_path).to exist
inode = target_path.stat.ino
expect(contents_path).to exist
app.uninstall_phase(command: command, force: force, successor: cask)
2023-04-04 10:21:44 -05:00
expect(target_path).to exist
expect(target_path.children).to be_empty
expect(contents_path).not_to exist
app.install_phase(command: command, adopt: adopt, force: force, predecessor: cask)
2023-04-04 10:21:44 -05:00
expect(target_path).to exist
expect(target_path.stat.ino).to eq(inode)
expect(contents_path).to exist
end
describe "when the system blocks modifying apps" do
it "uninstalls and reinstalls the app" do
target_contents_path = target_path.join("Contents")
expect(File).to receive(:write).with(target_path / ".homebrew-write-test",
instance_of(String)).and_raise(Errno::EACCES)
app.uninstall_phase(command: command, force: force, successor: cask)
expect(target_path).not_to exist
app.install_phase(command: command, adopt: adopt, force: force, predecessor: cask)
expect(target_contents_path).to exist
end
end
2023-05-12 09:57:12 -05:00
describe "when the directory is owned by root" do
before do
allow(app.target).to receive_messages(writable?: false, owned?: false)
end
it "reuses the same directory" do
source_contents_path = source_path.join("Contents")
target_contents_path = target_path.join("Contents")
allow(command).to receive(:run!).with(any_args).and_call_original
expect(command).to receive(:run!)
.with("/bin/cp", args: ["-pR", source_contents_path, target_path],
sudo: true)
.and_call_original
expect(FileUtils).not_to receive(:move).with(source_contents_path, an_instance_of(Pathname))
app.uninstall_phase(command: command, force: force, successor: cask)
expect(target_contents_path).not_to exist
expect(target_path).to exist
expect(source_contents_path).to exist
app.install_phase(command: command, adopt: adopt, force: force, predecessor: cask)
expect(target_contents_path).to exist
end
describe "when the system blocks modifying apps" do
it "uninstalls and reinstalls the app" do
target_contents_path = target_path.join("Contents")
allow(command).to receive(:run!).with(any_args).and_call_original
expect(command).to receive(:run!)
.with("touch", args: [target_path / ".homebrew-write-test"],
print_stderr: false,
sudo: true)
.and_raise(ErrorDuringExecution.new([], status: 1,
output: [[:stderr, "touch: #{target_path}/.homebrew-write-test: Operation not permitted\n"]], secrets: []))
app.uninstall_phase(command: command, force: force, successor: cask)
expect(target_path).not_to exist
app.install_phase(command: command, adopt: adopt, force: force, predecessor: cask)
expect(target_contents_path).to exist
end
end
end
2023-04-04 10:21:44 -05:00
end
2016-08-18 22:11:42 +03:00
end