
We've already disabled installing casks/formulae from URLs and we regularly tell people not to install from paths so let's just deprecate this behaviour entirely. Even Homebrew developers do not need to work this way.
454 lines
16 KiB
Ruby
454 lines
16 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
RSpec.describe Cask::Cask, :cask do
|
|
let(:cask) { described_class.new("versioned-cask") }
|
|
|
|
context "when multiple versions are installed" do
|
|
describe "#installed_version" do
|
|
context "when there are duplicate versions" do
|
|
it "uses the last unique version" do
|
|
allow(cask).to receive(:timestamped_versions).and_return([
|
|
["1.2.2", "0999"],
|
|
["1.2.3", "1000"],
|
|
["1.2.2", "1001"],
|
|
])
|
|
|
|
# Installed caskfile must exist to count as installed.
|
|
allow_any_instance_of(Pathname).to receive(:exist?).and_return(true)
|
|
|
|
expect(cask).to receive(:timestamped_versions)
|
|
expect(cask.installed_version).to eq("1.2.2")
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "load" do
|
|
let(:tap_path) { CoreCaskTap.instance.path }
|
|
let(:file_dirname) { Pathname.new(__FILE__).dirname }
|
|
let(:relative_tap_path) { tap_path.relative_path_from(file_dirname) }
|
|
|
|
it "returns an instance of the Cask for the given token" do
|
|
c = Cask::CaskLoader.load("local-caffeine")
|
|
expect(c).to be_a(described_class)
|
|
expect(c.token).to eq("local-caffeine")
|
|
end
|
|
|
|
it "returns an instance of the Cask from a specific file location" do
|
|
c = Cask::CaskLoader.load("#{tap_path}/Casks/local-caffeine.rb")
|
|
expect(c).to be_a(described_class)
|
|
expect(c.token).to eq("local-caffeine")
|
|
end
|
|
|
|
it "returns an instance of the Cask from a JSON file" do
|
|
c = Cask::CaskLoader.load("#{TEST_FIXTURE_DIR}/cask/caffeine.json")
|
|
expect(c).to be_a(described_class)
|
|
expect(c.token).to eq("caffeine")
|
|
end
|
|
|
|
it "raises an error when failing to download a Cask from a URL", :needs_utils_curl, :no_api do
|
|
expect do
|
|
Cask::CaskLoader.load("file://#{tap_path}/Casks/notacask.rb")
|
|
end.to raise_error(MethodDeprecatedError)
|
|
end
|
|
|
|
it "returns an instance of the Cask from a relative file location" do
|
|
c = Cask::CaskLoader.load(relative_tap_path/"Casks/local-caffeine.rb")
|
|
expect(c).to be_a(described_class)
|
|
expect(c.token).to eq("local-caffeine")
|
|
end
|
|
|
|
it "uses exact match when loading by token" do
|
|
expect(Cask::CaskLoader.load("test-opera").token).to eq("test-opera")
|
|
expect(Cask::CaskLoader.load("test-opera-mail").token).to eq("test-opera-mail")
|
|
end
|
|
|
|
it "raises an error when attempting to load a Cask that doesn't exist" do
|
|
expect do
|
|
Cask::CaskLoader.load("notacask")
|
|
end.to raise_error(Cask::CaskUnavailableError)
|
|
end
|
|
end
|
|
|
|
describe "metadata" do
|
|
it "proposes a versioned metadata directory name for each instance" do
|
|
cask_token = "local-caffeine"
|
|
c = Cask::CaskLoader.load(cask_token)
|
|
metadata_timestamped_path = Cask::Caskroom.path.join(cask_token, ".metadata", c.version)
|
|
expect(c.metadata_versioned_path.to_s).to eq(metadata_timestamped_path.to_s)
|
|
end
|
|
end
|
|
|
|
describe "outdated" do
|
|
it "ignores the Casks that have auto_updates true (without --greedy)" do
|
|
c = Cask::CaskLoader.load("auto-updates")
|
|
expect(c).not_to be_outdated
|
|
expect(c.outdated_version).to be_nil
|
|
end
|
|
|
|
it "ignores the Casks that have version :latest (without --greedy)" do
|
|
c = Cask::CaskLoader.load("version-latest-string")
|
|
expect(c).not_to be_outdated
|
|
expect(c.outdated_version).to be_nil
|
|
end
|
|
|
|
describe "versioned casks" do
|
|
subject { cask.outdated_version }
|
|
|
|
let(:cask) { described_class.new("basic-cask") }
|
|
|
|
shared_examples "versioned casks" do |tap_version, expectations|
|
|
expectations.each do |installed_version, expected_output|
|
|
context "when version #{installed_version.inspect} is installed and the tap version is #{tap_version}" do
|
|
it {
|
|
allow(cask).to receive_messages(installed_version:,
|
|
version: Cask::DSL::Version.new(tap_version))
|
|
expect(cask).to receive(:outdated_version).and_call_original
|
|
expect(subject).to eq expected_output
|
|
}
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "installed version is equal to tap version => not outdated" do
|
|
include_examples "versioned casks", "1.2.3",
|
|
"1.2.3" => nil
|
|
end
|
|
|
|
describe "installed version is different than tap version => outdated" do
|
|
include_examples "versioned casks", "1.2.4",
|
|
"1.2.3" => "1.2.3",
|
|
"1.2.4" => nil
|
|
end
|
|
end
|
|
|
|
describe ":latest casks" do
|
|
let(:cask) { described_class.new("basic-cask") }
|
|
|
|
shared_examples ":latest cask" do |greedy, outdated_sha, tap_version, expectations|
|
|
expectations.each do |installed_version, expected_output|
|
|
context "when versions #{installed_version} are installed and the " \
|
|
"tap version is #{tap_version}, #{"not " unless greedy}greedy " \
|
|
"and sha is #{"not " unless outdated_sha}outdated" do
|
|
subject { cask.outdated_version(greedy:) }
|
|
|
|
it {
|
|
allow(cask).to receive_messages(installed_version:,
|
|
version: Cask::DSL::Version.new(tap_version),
|
|
outdated_download_sha?: outdated_sha)
|
|
expect(cask).to receive(:outdated_version).and_call_original
|
|
expect(subject).to eq expected_output
|
|
}
|
|
end
|
|
end
|
|
end
|
|
|
|
describe ":latest version installed, :latest version in tap" do
|
|
include_examples ":latest cask", false, false, "latest",
|
|
"latest" => nil
|
|
include_examples ":latest cask", true, false, "latest",
|
|
"latest" => nil
|
|
include_examples ":latest cask", true, true, "latest",
|
|
"latest" => "latest"
|
|
end
|
|
|
|
describe "numbered version installed, :latest version in tap" do
|
|
include_examples ":latest cask", false, false, "latest",
|
|
"1.2.3" => nil
|
|
include_examples ":latest cask", true, false, "latest",
|
|
"1.2.3" => nil
|
|
include_examples ":latest cask", true, true, "latest",
|
|
"1.2.3" => "1.2.3"
|
|
end
|
|
|
|
describe "latest version installed, numbered version in tap" do
|
|
include_examples ":latest cask", false, false, "1.2.3",
|
|
"latest" => "latest"
|
|
include_examples ":latest cask", true, false, "1.2.3",
|
|
"latest" => "latest"
|
|
include_examples ":latest cask", true, true, "1.2.3",
|
|
"latest" => "latest"
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "full_name" do
|
|
context "when it is a core cask" do
|
|
it "is the cask token" do
|
|
c = Cask::CaskLoader.load("local-caffeine")
|
|
expect(c.full_name).to eq("local-caffeine")
|
|
end
|
|
end
|
|
|
|
context "when it is from a non-core tap" do
|
|
it "returns the fully-qualified name of the cask" do
|
|
c = Cask::CaskLoader.load("third-party/tap/third-party-cask")
|
|
expect(c.full_name).to eq("third-party/tap/third-party-cask")
|
|
end
|
|
end
|
|
|
|
context "when it is from no known tap" do
|
|
it "returns the cask token" do
|
|
file = Tempfile.new(%w[tapless-cask .rb])
|
|
|
|
begin
|
|
cask_name = File.basename(file.path, ".rb")
|
|
file.write "cask '#{cask_name}'"
|
|
file.close
|
|
|
|
c = Cask::CaskLoader.load(file.path)
|
|
expect(c.full_name).to eq(cask_name)
|
|
ensure
|
|
file.close
|
|
file.unlink
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "#artifacts_list" do
|
|
subject(:cask) { Cask::CaskLoader.load("many-artifacts") }
|
|
|
|
it "returns all artifacts when no options are given" do
|
|
expected_artifacts = [
|
|
{ uninstall_preflight: nil },
|
|
{ preflight: nil },
|
|
{ uninstall: [{
|
|
rmdir: "#{TEST_TMPDIR}/empty_directory_path",
|
|
trash: ["#{TEST_TMPDIR}/foo", "#{TEST_TMPDIR}/bar"],
|
|
}] },
|
|
{ pkg: ["ManyArtifacts/ManyArtifacts.pkg"] },
|
|
{ app: ["ManyArtifacts/ManyArtifacts.app"] },
|
|
{ uninstall_postflight: nil },
|
|
{ postflight: nil },
|
|
{ zap: [{
|
|
rmdir: ["~/Library/Caches/ManyArtifacts", "~/Library/Application Support/ManyArtifacts"],
|
|
trash: "~/Library/Logs/ManyArtifacts.log",
|
|
}] },
|
|
]
|
|
|
|
expect(cask.artifacts_list).to eq(expected_artifacts)
|
|
end
|
|
|
|
it "skips flight blocks when compact is true" do
|
|
expected_artifacts = [
|
|
{ uninstall: [{
|
|
rmdir: "#{TEST_TMPDIR}/empty_directory_path",
|
|
trash: ["#{TEST_TMPDIR}/foo", "#{TEST_TMPDIR}/bar"],
|
|
}] },
|
|
{ pkg: ["ManyArtifacts/ManyArtifacts.pkg"] },
|
|
{ app: ["ManyArtifacts/ManyArtifacts.app"] },
|
|
{ zap: [{
|
|
rmdir: ["~/Library/Caches/ManyArtifacts", "~/Library/Application Support/ManyArtifacts"],
|
|
trash: "~/Library/Logs/ManyArtifacts.log",
|
|
}] },
|
|
]
|
|
|
|
expect(cask.artifacts_list(compact: true)).to eq(expected_artifacts)
|
|
end
|
|
|
|
it "returns only uninstall artifacts when uninstall_only is true" do
|
|
expected_artifacts = [
|
|
{ uninstall_preflight: nil },
|
|
{ uninstall: [{
|
|
rmdir: "#{TEST_TMPDIR}/empty_directory_path",
|
|
trash: ["#{TEST_TMPDIR}/foo", "#{TEST_TMPDIR}/bar"],
|
|
}] },
|
|
{ app: ["ManyArtifacts/ManyArtifacts.app"] },
|
|
{ uninstall_postflight: nil },
|
|
{ zap: [{
|
|
rmdir: ["~/Library/Caches/ManyArtifacts", "~/Library/Application Support/ManyArtifacts"],
|
|
trash: "~/Library/Logs/ManyArtifacts.log",
|
|
}] },
|
|
]
|
|
|
|
expect(cask.artifacts_list(uninstall_only: true)).to eq(expected_artifacts)
|
|
end
|
|
|
|
it "skips flight blocks and returns only uninstall artifacts when compact and uninstall_only are true" do
|
|
expected_artifacts = [
|
|
{ uninstall: [{
|
|
rmdir: "#{TEST_TMPDIR}/empty_directory_path",
|
|
trash: ["#{TEST_TMPDIR}/foo", "#{TEST_TMPDIR}/bar"],
|
|
}] },
|
|
{ app: ["ManyArtifacts/ManyArtifacts.app"] },
|
|
{ zap: [{
|
|
rmdir: ["~/Library/Caches/ManyArtifacts", "~/Library/Application Support/ManyArtifacts"],
|
|
trash: "~/Library/Logs/ManyArtifacts.log",
|
|
}] },
|
|
]
|
|
|
|
expect(cask.artifacts_list(compact: true, uninstall_only: true)).to eq(expected_artifacts)
|
|
end
|
|
end
|
|
|
|
describe "#uninstall_flight_blocks?" do
|
|
matcher :have_uninstall_flight_blocks do
|
|
match do |actual|
|
|
actual.uninstall_flight_blocks? == true
|
|
end
|
|
end
|
|
|
|
it "returns true when there are uninstall_preflight blocks" do
|
|
cask = Cask::CaskLoader.load("with-uninstall-preflight")
|
|
expect(cask).to have_uninstall_flight_blocks
|
|
end
|
|
|
|
it "returns true when there are uninstall_postflight blocks" do
|
|
cask = Cask::CaskLoader.load("with-uninstall-postflight")
|
|
expect(cask).to have_uninstall_flight_blocks
|
|
end
|
|
|
|
it "returns false when there are only preflight blocks" do
|
|
cask = Cask::CaskLoader.load("with-preflight")
|
|
expect(cask).not_to have_uninstall_flight_blocks
|
|
end
|
|
|
|
it "returns false when there are only postflight blocks" do
|
|
cask = Cask::CaskLoader.load("with-postflight")
|
|
expect(cask).not_to have_uninstall_flight_blocks
|
|
end
|
|
|
|
it "returns false when there are no flight blocks" do
|
|
cask = Cask::CaskLoader.load("local-caffeine")
|
|
expect(cask).not_to have_uninstall_flight_blocks
|
|
end
|
|
end
|
|
|
|
describe "#to_h" do
|
|
let(:expected_json) { (TEST_FIXTURE_DIR/"cask/everything.json").read.strip }
|
|
|
|
context "when loaded from cask file" do
|
|
it "returns expected hash" do
|
|
allow(MacOS).to receive(:version).and_return(MacOSVersion.new("13"))
|
|
|
|
cask = Cask::CaskLoader.load("everything")
|
|
|
|
expect(cask.tap).to receive(:git_head).and_return("abcdef1234567890abcdef1234567890abcdef12")
|
|
|
|
hash = cask.to_h
|
|
|
|
expect(hash).to be_a(Hash)
|
|
expect(JSON.pretty_generate(hash)).to eq(expected_json)
|
|
end
|
|
end
|
|
|
|
context "when loaded from json file" do
|
|
it "returns expected hash" do
|
|
expect(Homebrew::API::Cask).not_to receive(:source_download)
|
|
hash = Cask::CaskLoader::FromAPILoader.new(
|
|
"everything", from_json: JSON.parse(expected_json)
|
|
).load(config: nil).to_h
|
|
|
|
expect(hash).to be_a(Hash)
|
|
expect(JSON.pretty_generate(hash)).to eq(expected_json)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "#to_hash_with_variations" do
|
|
let!(:original_macos_version) { MacOS.full_version.to_s }
|
|
let(:expected_versions_variations) do
|
|
<<~JSON
|
|
{
|
|
"monterey": {
|
|
"url": "file://#{TEST_FIXTURE_DIR}/cask/caffeine/darwin/1.2.3/intel.zip"
|
|
},
|
|
"big_sur": {
|
|
"url": "file://#{TEST_FIXTURE_DIR}/cask/caffeine/darwin/1.2.0/intel.zip",
|
|
"version": "1.2.0",
|
|
"sha256": "8c62a2b791cf5f0da6066a0a4b6e85f62949cd60975da062df44adf887f4370b"
|
|
},
|
|
"arm64_big_sur": {
|
|
"url": "file://#{TEST_FIXTURE_DIR}/cask/caffeine/darwin-arm64/1.2.0/arm.zip",
|
|
"version": "1.2.0",
|
|
"sha256": "8c62a2b791cf5f0da6066a0a4b6e85f62949cd60975da062df44adf887f4370b"
|
|
},
|
|
"catalina": {
|
|
"url": "file://#{TEST_FIXTURE_DIR}/cask/caffeine/darwin/1.0.0/intel.zip",
|
|
"version": "1.0.0",
|
|
"sha256": "1866dfa833b123bb8fe7fa7185ebf24d28d300d0643d75798bc23730af734216"
|
|
},
|
|
"mojave": {
|
|
"url": "file://#{TEST_FIXTURE_DIR}/cask/caffeine/darwin/1.0.0/intel.zip",
|
|
"version": "1.0.0",
|
|
"sha256": "1866dfa833b123bb8fe7fa7185ebf24d28d300d0643d75798bc23730af734216"
|
|
}
|
|
}
|
|
JSON
|
|
end
|
|
let(:expected_sha256_variations) do
|
|
<<~JSON
|
|
{
|
|
"monterey": {
|
|
"url": "file://#{TEST_FIXTURE_DIR}/cask/caffeine-intel.zip",
|
|
"sha256": "8c62a2b791cf5f0da6066a0a4b6e85f62949cd60975da062df44adf887f4370b"
|
|
},
|
|
"big_sur": {
|
|
"url": "file://#{TEST_FIXTURE_DIR}/cask/caffeine-intel.zip",
|
|
"sha256": "8c62a2b791cf5f0da6066a0a4b6e85f62949cd60975da062df44adf887f4370b"
|
|
},
|
|
"catalina": {
|
|
"url": "file://#{TEST_FIXTURE_DIR}/cask/caffeine-intel.zip",
|
|
"sha256": "8c62a2b791cf5f0da6066a0a4b6e85f62949cd60975da062df44adf887f4370b"
|
|
},
|
|
"mojave": {
|
|
"url": "file://#{TEST_FIXTURE_DIR}/cask/caffeine-intel.zip",
|
|
"sha256": "8c62a2b791cf5f0da6066a0a4b6e85f62949cd60975da062df44adf887f4370b"
|
|
}
|
|
}
|
|
JSON
|
|
end
|
|
|
|
before do
|
|
# Use a more limited symbols list to shorten the variations hash
|
|
symbols = {
|
|
monterey: "12",
|
|
big_sur: "11",
|
|
catalina: "10.15",
|
|
mojave: "10.14",
|
|
}
|
|
stub_const("MacOSVersion::SYMBOLS", symbols)
|
|
|
|
# For consistency, always run on Monterey and ARM
|
|
MacOS.full_version = "12"
|
|
allow(Hardware::CPU).to receive(:type).and_return(:arm)
|
|
end
|
|
|
|
after do
|
|
MacOS.full_version = original_macos_version
|
|
end
|
|
|
|
it "returns the correct variations hash for a cask with multiple versions" do
|
|
c = Cask::CaskLoader.load("multiple-versions")
|
|
h = c.to_hash_with_variations
|
|
|
|
expect(h).to be_a(Hash)
|
|
expect(JSON.pretty_generate(h["variations"])).to eq expected_versions_variations.strip
|
|
end
|
|
|
|
it "returns the correct variations hash for a cask different sha256s on each arch" do
|
|
c = Cask::CaskLoader.load("sha256-arch")
|
|
h = c.to_hash_with_variations
|
|
|
|
expect(h).to be_a(Hash)
|
|
expect(JSON.pretty_generate(h["variations"])).to eq expected_sha256_variations.strip
|
|
end
|
|
|
|
# NOTE: The calls to `Cask.generating_hash!` and `Cask.generated_hash!`
|
|
# are not idempotent so they can only be used in one test.
|
|
it "returns the correct hash placeholders" do
|
|
described_class.generating_hash!
|
|
expect(described_class).to be_generating_hash
|
|
c = Cask::CaskLoader.load("placeholders")
|
|
h = c.to_hash_with_variations
|
|
described_class.generated_hash!
|
|
expect(described_class).not_to be_generating_hash
|
|
|
|
expect(h).to be_a(Hash)
|
|
expect(h["artifacts"].first[:binary].first).to eq "$APPDIR/some/path"
|
|
expect(h["caveats"]).to eq "$HOMEBREW_PREFIX and /$HOME\n"
|
|
end
|
|
end
|
|
end
|