aboutsummaryrefslogtreecommitdiffstats
path: root/Library/Homebrew/test
diff options
context:
space:
mode:
authorMarkus Reiter2017-03-05 06:31:36 +0100
committerMarkus Reiter2017-03-05 23:08:14 +0100
commit9fc6c7b2be300ff35dc52d80f4dc38d36d52ddc2 (patch)
tree43e99a683329471c1dc965dcc92daccb57df7e8d /Library/Homebrew/test
parent67ec76d1492fbb03959a782a85c4fb985d6a5884 (diff)
downloadbrew-9fc6c7b2be300ff35dc52d80f4dc38d36d52ddc2.tar.bz2
Move Cask specs into `brew tests`.
Diffstat (limited to 'Library/Homebrew/test')
-rw-r--r--Library/Homebrew/test/cask/accessibility_spec.rb80
-rw-r--r--Library/Homebrew/test/cask/artifact/alt_target_spec.rb77
-rw-r--r--Library/Homebrew/test/cask/artifact/app_spec.rb243
-rw-r--r--Library/Homebrew/test/cask/artifact/binary_spec.rb90
-rw-r--r--Library/Homebrew/test/cask/artifact/generic_artifact_spec.rb45
-rw-r--r--Library/Homebrew/test/cask/artifact/nested_container_spec.rb15
-rw-r--r--Library/Homebrew/test/cask/artifact/pkg_spec.rb69
-rw-r--r--Library/Homebrew/test/cask/artifact/postflight_block_spec.rb39
-rw-r--r--Library/Homebrew/test/cask/artifact/preflight_block_spec.rb39
-rw-r--r--Library/Homebrew/test/cask/artifact/suite_spec.rb47
-rw-r--r--Library/Homebrew/test/cask/artifact/two_apps_correct_spec.rb91
-rw-r--r--Library/Homebrew/test/cask/artifact/two_apps_incorrect_spec.rb9
-rw-r--r--Library/Homebrew/test/cask/artifact/uninstall_no_zap_spec.rb19
-rw-r--r--Library/Homebrew/test/cask/artifact/uninstall_spec.rb351
-rw-r--r--Library/Homebrew/test/cask/artifact/zap_spec.rb352
-rw-r--r--Library/Homebrew/test/cask/audit_spec.rb364
-rw-r--r--Library/Homebrew/test/cask/cask_spec.rb92
-rw-r--r--Library/Homebrew/test/cask/cli/audit_spec.rb59
-rw-r--r--Library/Homebrew/test/cask/cli/cat_spec.rb57
-rw-r--r--Library/Homebrew/test/cask/cli/cleanup_spec.rb93
-rw-r--r--Library/Homebrew/test/cask/cli/create_spec.rb98
-rw-r--r--Library/Homebrew/test/cask/cli/doctor_spec.rb15
-rw-r--r--Library/Homebrew/test/cask/cli/edit_spec.rb60
-rw-r--r--Library/Homebrew/test/cask/cli/fetch_spec.rb75
-rw-r--r--Library/Homebrew/test/cask/cli/home_spec.rb46
-rw-r--r--Library/Homebrew/test/cask/cli/info_spec.rb108
-rw-r--r--Library/Homebrew/test/cask/cli/install_spec.rb107
-rw-r--r--Library/Homebrew/test/cask/cli/list_spec.rb82
-rw-r--r--Library/Homebrew/test/cask/cli/options_spec.rb138
-rw-r--r--Library/Homebrew/test/cask/cli/reinstall_spec.rb22
-rw-r--r--Library/Homebrew/test/cask/cli/search_spec.rb59
-rw-r--r--Library/Homebrew/test/cask/cli/style_spec.rb235
-rw-r--r--Library/Homebrew/test/cask/cli/uninstall_spec.rb189
-rw-r--r--Library/Homebrew/test/cask/cli/version_spec.rb9
-rw-r--r--Library/Homebrew/test/cask/cli/zap_spec.rb73
-rw-r--r--Library/Homebrew/test/cask/cli_spec.rb61
-rw-r--r--Library/Homebrew/test/cask/container/dmg_spec.rb20
-rw-r--r--Library/Homebrew/test/cask/container/naked_spec.rb23
-rw-r--r--Library/Homebrew/test/cask/depends_on_spec.rb90
-rw-r--r--Library/Homebrew/test/cask/download_strategy_spec.rb306
-rw-r--r--Library/Homebrew/test/cask/dsl/caveats_spec.rb10
-rw-r--r--Library/Homebrew/test/cask/dsl/postflight_spec.rb13
-rw-r--r--Library/Homebrew/test/cask/dsl/preflight_spec.rb13
-rw-r--r--Library/Homebrew/test/cask/dsl/stanza_proxy_spec.rb32
-rw-r--r--Library/Homebrew/test/cask/dsl/uninstall_postflight_spec.rb8
-rw-r--r--Library/Homebrew/test/cask/dsl/uninstall_preflight_spec.rb13
-rw-r--r--Library/Homebrew/test/cask/dsl/version_spec.rb232
-rw-r--r--Library/Homebrew/test/cask/dsl_spec.rb546
-rw-r--r--Library/Homebrew/test/cask/installer_spec.rb396
-rw-r--r--Library/Homebrew/test/cask/macos_spec.rb67
-rw-r--r--Library/Homebrew/test/cask/pkg_spec.rb112
-rw-r--r--Library/Homebrew/test/cask/scopes_spec.rb37
-rw-r--r--Library/Homebrew/test/cask/staged_spec.rb16
-rw-r--r--Library/Homebrew/test/cask/system_command_result_spec.rb95
-rw-r--r--Library/Homebrew/test/cask/system_command_spec.rb139
-rw-r--r--Library/Homebrew/test/cask/underscore_supporting_uri_spec.rb14
-rw-r--r--Library/Homebrew/test/cask/url_checker_spec.rb42
-rw-r--r--Library/Homebrew/test/cask/verify/checksum_spec.rb91
-rw-r--r--Library/Homebrew/test/cask/verify_spec.rb63
-rw-r--r--Library/Homebrew/test/spec_helper.rb2
-rw-r--r--Library/Homebrew/test/support/helper/spec/shared_context/homebrew_cask.rb45
61 files changed, 6033 insertions, 0 deletions
diff --git a/Library/Homebrew/test/cask/accessibility_spec.rb b/Library/Homebrew/test/cask/accessibility_spec.rb
new file mode 100644
index 000000000..4ac757e69
--- /dev/null
+++ b/Library/Homebrew/test/cask/accessibility_spec.rb
@@ -0,0 +1,80 @@
+# TODO: this test should be named after the corresponding class, once
+# that class is abstracted from installer.rb.
+describe "Accessibility Access", :cask do
+ let(:cask) { Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/with-accessibility-access.rb") }
+ let(:fake_system_command) { class_double(Hbc::SystemCommand) }
+ let(:installer) { Hbc::Installer.new(cask, command: fake_system_command) }
+
+ before(:each) do
+ allow(MacOS).to receive(:version).and_return(MacOS::Version.new(macos_version))
+ allow(installer).to receive(:bundle_identifier).and_return("com.example.BasicCask")
+ end
+
+ context "on MacOS 10.8 and below" do
+ let(:macos_version) { "10.8" }
+
+ it "can enable accessibility access in macOS releases prior to Mavericks" do
+ expect(fake_system_command).to receive(:run!).with(
+ "/usr/bin/touch",
+ args: [Hbc.pre_mavericks_accessibility_dotfile],
+ sudo: true,
+ )
+
+ shutup do
+ installer.enable_accessibility_access
+ end
+ end
+
+ it "warns about disabling accessibility access on old macOS releases" do
+ expect {
+ installer.disable_accessibility_access
+ }.to output(/Warning: Accessibility access cannot be disabled automatically on this version of macOS\./).to_stderr
+ end
+ end
+
+ context "on MacOS 10.9" do
+ let(:macos_version) { "10.9" }
+
+ it "can enable accessibility access" do
+ expect(fake_system_command).to receive(:run!).with(
+ "/usr/bin/sqlite3",
+ args: [Hbc.tcc_db, "INSERT OR REPLACE INTO access VALUES('kTCCServiceAccessibility','com.example.BasicCask',0,1,1,NULL);"],
+ sudo: true,
+ )
+
+ shutup do
+ installer.enable_accessibility_access
+ end
+ end
+
+ it "can disable accessibility access" do
+ expect(fake_system_command).to receive(:run!).with(
+ "/usr/bin/sqlite3",
+ args: [Hbc.tcc_db, "DELETE FROM access WHERE client='com.example.BasicCask';"],
+ sudo: true,
+ )
+
+ shutup do
+ installer.disable_accessibility_access
+ end
+ end
+ end
+
+ context "on MacOS 10.12 and above" do
+ let(:macos_version) { "10.12" }
+
+ it "warns about enabling accessibility access on new macOS releases" do
+ expect {
+ expect {
+ installer.enable_accessibility_access
+ }.to output.to_stdout
+ }.to output(/Warning: Accessibility access cannot be enabled automatically on this version of macOS\./).to_stderr
+ end
+
+ it "warns about disabling accessibility access on new macOS releases" do
+ expect {
+ installer.disable_accessibility_access
+ }.to output(/Warning: Accessibility access cannot be disabled automatically on this version of macOS\./).to_stderr
+ end
+ end
+end
diff --git a/Library/Homebrew/test/cask/artifact/alt_target_spec.rb b/Library/Homebrew/test/cask/artifact/alt_target_spec.rb
new file mode 100644
index 000000000..9bcdd27a1
--- /dev/null
+++ b/Library/Homebrew/test/cask/artifact/alt_target_spec.rb
@@ -0,0 +1,77 @@
+describe Hbc::Artifact::App, :cask do
+ describe "activate to alternate target" do
+ let(:cask) { Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/with-alt-target.rb") }
+
+ let(:install_phase) {
+ -> { Hbc::Artifact::App.new(cask).install_phase }
+ }
+
+ let(:source_path) { cask.staged_path.join("Caffeine.app") }
+ let(:target_path) { Hbc.appdir.join("AnotherName.app") }
+
+ before do
+ InstallHelper.install_without_artifacts(cask)
+ end
+
+ it "installs the given apps using the proper target directory" do
+ expect(source_path).to be_a_directory
+ expect(target_path).not_to exist
+
+ shutup do
+ install_phase.call
+ end
+
+ expect(target_path).to be_a_directory
+ expect(source_path).not_to exist
+ end
+
+ describe "when app is in a subdirectory" do
+ let(:cask) {
+ Hbc::Cask.new("subdir") do
+ url "file://#{TEST_FIXTURE_DIR}/cask/caffeine.zip"
+ homepage "http://example.com/local-caffeine"
+ version "1.2.3"
+ sha256 "67cdb8a02803ef37fdbf7e0be205863172e41a561ca446cd84f0d7ab35a99d94"
+ app "subdir/Caffeine.app", target: "AnotherName.app"
+ end
+ }
+
+ it "installs the given apps using the proper target directory" do
+ appsubdir = cask.staged_path.join("subdir").tap(&:mkpath)
+ FileUtils.mv(source_path, appsubdir)
+
+ shutup do
+ install_phase.call
+ end
+
+ expect(target_path).to be_a_directory
+ expect(appsubdir.join("Caffeine.app")).not_to exist
+ 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
+
+ shutup do
+ install_phase.call
+ end
+
+ expect(target_path).to be_a_directory
+ expect(source_path).not_to exist
+
+ expect(Hbc.appdir.join("Caffeine Deluxe.app")).not_to exist
+ expect(cask.staged_path.join("Caffeine Deluxe.app")).to be_a_directory
+ end
+
+ it "avoids clobbering an existing app by moving over it" do
+ target_path.mkpath
+
+ expect(install_phase).to raise_error(Hbc::CaskError, "It seems there is already an App at '#{target_path}'.")
+
+ expect(source_path).to be_a_directory
+ expect(target_path).to be_a_directory
+ expect(File.identical?(source_path, target_path)).to be false
+ end
+ end
+end
diff --git a/Library/Homebrew/test/cask/artifact/app_spec.rb b/Library/Homebrew/test/cask/artifact/app_spec.rb
new file mode 100644
index 000000000..bfd2d5cd4
--- /dev/null
+++ b/Library/Homebrew/test/cask/artifact/app_spec.rb
@@ -0,0 +1,243 @@
+describe Hbc::Artifact::App, :cask do
+ let(:cask) { Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/local-caffeine.rb") }
+ let(:command) { Hbc::SystemCommand }
+ let(:force) { false }
+ let(:app) { Hbc::Artifact::App.new(cask, command: command, force: force) }
+
+ let(:source_path) { cask.staged_path.join("Caffeine.app") }
+ let(:target_path) { Hbc.appdir.join("Caffeine.app") }
+
+ let(:install_phase) { -> { app.install_phase } }
+ let(:uninstall_phase) { -> { app.uninstall_phase } }
+
+ before(:each) do
+ InstallHelper.install_without_artifacts(cask)
+ end
+
+ describe "install_phase" do
+ it "installs the given app using the proper target directory" do
+ shutup do
+ install_phase.call
+ end
+
+ expect(target_path).to be_a_directory
+ expect(source_path).not_to exist
+ end
+
+ describe "when app is in a subdirectory" do
+ let(:cask) {
+ Hbc::Cask.new("subdir") do
+ url "file://#{TEST_FIXTURE_DIR}/cask/caffeine.zip"
+ homepage "http://example.com/local-caffeine"
+ version "1.2.3"
+ sha256 "67cdb8a02803ef37fdbf7e0be205863172e41a561ca446cd84f0d7ab35a99d94"
+ app "subdir/Caffeine.app"
+ end
+ }
+
+ it "installs the given app using the proper target directory" do
+ appsubdir = cask.staged_path.join("subdir").tap(&:mkpath)
+ FileUtils.mv(source_path, appsubdir)
+
+ shutup do
+ install_phase.call
+ end
+
+ expect(target_path).to be_a_directory
+ expect(appsubdir.join("Caffeine.app")).not_to exist
+ 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
+
+ shutup do
+ install_phase.call
+ end
+
+ expect(target_path).to be_a_directory
+ expect(source_path).not_to exist
+
+ expect(Hbc.appdir.join("Caffeine Deluxe.app")).not_to exist
+ expect(cask.staged_path.join("Caffeine Deluxe.app")).to exist
+ end
+
+ describe "when the target already exists" do
+ before(:each) do
+ target_path.mkpath
+ end
+
+ it "avoids clobbering an existing app" do
+ expect(install_phase).to raise_error(Hbc::CaskError, "It seems there is already an App at '#{target_path}'.")
+
+ 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
+
+ describe "given the force option" do
+ let(:force) { true }
+
+ before(:each) do
+ allow(Hbc::Utils).to receive(:current_user).and_return("fake_user")
+ end
+
+ describe "target is both writable and user-owned" do
+ it "overwrites the existing app" do
+ stdout = <<-EOS.undent
+ ==> Removing App: '#{target_path}'
+ ==> Moving App 'Caffeine.app' to '#{target_path}'
+ EOS
+
+ stderr = <<-EOS.undent
+ Warning: It seems there is already an App at '#{target_path}'; overwriting.
+ EOS
+
+ expect {
+ expect(install_phase).to output(stdout).to_stdout
+ }.to output(stderr).to_stderr
+
+ expect(source_path).not_to exist
+ expect(target_path).to be_a_directory
+
+ contents_path = target_path.join("Contents/Info.plist")
+ expect(contents_path).to exist
+ end
+ end
+
+ describe "target is user-owned but contains read-only files" do
+ before(:each) do
+ system "/usr/bin/touch", "--", "#{target_path}/foo"
+ system "/bin/chmod", "--", "0555", target_path
+ end
+
+ it "overwrites the existing app" do
+ expect(command).to receive(:run).with("/bin/chmod", args: ["-R", "--", "u+rwx", target_path], must_succeed: false)
+ .and_call_original
+ expect(command).to receive(:run).with("/bin/chmod", args: ["-R", "-N", target_path], must_succeed: false)
+ .and_call_original
+ expect(command).to receive(:run).with("/usr/bin/chflags", args: ["-R", "--", "000", target_path], must_succeed: false)
+ .and_call_original
+
+ stdout = <<-EOS.undent
+ ==> Removing App: '#{target_path}'
+ ==> Moving App 'Caffeine.app' to '#{target_path}'
+ EOS
+
+ stderr = <<-EOS.undent
+ Warning: It seems there is already an App at '#{target_path}'; overwriting.
+ EOS
+
+ expect {
+ expect(install_phase).to output(stdout).to_stdout
+ }.to output(stderr).to_stderr
+
+ expect(source_path).not_to exist
+ expect(target_path).to be_a_directory
+
+ contents_path = target_path.join("Contents/Info.plist")
+ expect(contents_path).to exist
+ end
+
+ after(:each) do
+ system "/bin/chmod", "--", "0755", target_path
+ end
+ end
+ end
+ end
+
+ describe "when the target is a broken symlink" do
+ let(:deleted_path) { cask.staged_path.join("Deleted.app") }
+
+ before(:each) do
+ 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(Hbc::CaskError, "It seems there is already an App at '#{target_path}'.")
+ expect(target_path).to be_a_symlink
+ end
+
+ describe "given the force option" do
+ let(:force) { true }
+
+ it "overwrites the existing app" do
+ stdout = <<-EOS.undent
+ ==> Removing App: '#{target_path}'
+ ==> Moving App 'Caffeine.app' to '#{target_path}'
+ EOS
+
+ stderr = <<-EOS.undent
+ Warning: It seems there is already an App at '#{target_path}'; overwriting.
+ EOS
+
+ expect {
+ expect(install_phase).to output(stdout).to_stdout
+ }.to output(stderr).to_stderr
+
+ expect(source_path).not_to exist
+ expect(target_path).to be_a_directory
+
+ contents_path = target_path.join("Contents/Info.plist")
+ expect(contents_path).to exist
+ end
+ end
+ end
+
+ it "gives a warning if the source doesn't exist" do
+ source_path.rmtree
+
+ message = "It seems the App source is not there: '#{source_path}'"
+
+ expect(install_phase).to raise_error(Hbc::CaskError, message)
+ end
+ end
+
+ describe "uninstall_phase" do
+ it "deletes managed apps" do
+ shutup do
+ install_phase.call
+ end
+
+ expect(target_path).to exist
+
+ shutup do
+ uninstall_phase.call
+ end
+
+ expect(target_path).not_to exist
+ end
+ end
+
+ describe "summary" do
+ let(:description) { app.summary[:english_description] }
+ let(:contents) { app.summary[:contents] }
+
+ it "returns the correct english_description" do
+ expect(description).to eq("Apps")
+ end
+
+ describe "app is correctly installed" do
+ it "returns the path to the app" do
+ shutup do
+ install_phase.call
+ end
+
+ expect(contents).to eq(["#{target_path} (#{target_path.abv})"])
+ end
+ end
+
+ describe "app is missing" do
+ it "returns a warning and the supposed path to the app" do
+ expect(contents.size).to eq(1)
+ expect(contents[0]).to match(/.*Missing App.*: #{target_path}/)
+ end
+ end
+ end
+end
diff --git a/Library/Homebrew/test/cask/artifact/binary_spec.rb b/Library/Homebrew/test/cask/artifact/binary_spec.rb
new file mode 100644
index 000000000..1b26773ca
--- /dev/null
+++ b/Library/Homebrew/test/cask/artifact/binary_spec.rb
@@ -0,0 +1,90 @@
+describe Hbc::Artifact::Binary, :cask do
+ let(:cask) {
+ Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/with-binary.rb").tap do |cask|
+ shutup do
+ InstallHelper.install_without_artifacts(cask)
+ end
+ end
+ }
+ let(:expected_path) {
+ Hbc.binarydir.join("binary")
+ }
+ before(:each) do
+ Hbc.binarydir.mkpath
+ end
+ after(:each) do
+ FileUtils.rm expected_path if expected_path.exist?
+ end
+
+ it "links the binary to the proper directory" do
+ shutup do
+ Hbc::Artifact::Binary.new(cask).install_phase
+ end
+ expect(expected_path).to be_a_symlink
+ expect(expected_path.readlink).to exist
+ end
+
+ it "avoids clobbering an existing binary by linking over it" do
+ FileUtils.touch expected_path
+
+ expect {
+ shutup do
+ Hbc::Artifact::Binary.new(cask).install_phase
+ end
+ }.to raise_error(Hbc::CaskError)
+
+ expect(expected_path).not_to be :symlink?
+ end
+
+ it "clobbers an existing symlink" do
+ expected_path.make_symlink("/tmp")
+
+ shutup do
+ Hbc::Artifact::Binary.new(cask).install_phase
+ end
+
+ expect(File.readlink(expected_path)).not_to eq("/tmp")
+ end
+
+ it "respects --no-binaries flag" do
+ Hbc.no_binaries = true
+
+ shutup do
+ Hbc::Artifact::Binary.new(cask).install_phase
+ end
+
+ expect(expected_path.exist?).to be false
+
+ Hbc.no_binaries = false
+ end
+
+ it "creates parent directory if it doesn't exist" do
+ FileUtils.rmdir Hbc.binarydir
+
+ shutup do
+ Hbc::Artifact::Binary.new(cask).install_phase
+ end
+
+ expect(expected_path.exist?).to be true
+ end
+
+ context "binary is inside an app package" do
+ let(:cask) {
+ Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/with-embedded-binary.rb").tap do |cask|
+ shutup do
+ InstallHelper.install_without_artifacts(cask)
+ end
+ end
+ }
+
+ it "links the binary to the proper directory" do
+ shutup do
+ Hbc::Artifact::App.new(cask).install_phase
+ Hbc::Artifact::Binary.new(cask).install_phase
+ end
+
+ expect(expected_path).to be_a_symlink
+ expect(expected_path.readlink).to exist
+ end
+ end
+end
diff --git a/Library/Homebrew/test/cask/artifact/generic_artifact_spec.rb b/Library/Homebrew/test/cask/artifact/generic_artifact_spec.rb
new file mode 100644
index 000000000..b383e2d4e
--- /dev/null
+++ b/Library/Homebrew/test/cask/artifact/generic_artifact_spec.rb
@@ -0,0 +1,45 @@
+describe Hbc::Artifact::Artifact, :cask do
+ let(:cask) { Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/with-generic-artifact.rb") }
+
+ let(:install_phase) {
+ -> { Hbc::Artifact::Artifact.new(cask).install_phase }
+ }
+
+ let(:source_path) { cask.staged_path.join("Caffeine.app") }
+ let(:target_path) { Hbc.appdir.join("Caffeine.app") }
+
+ before do
+ InstallHelper.install_without_artifacts(cask)
+ end
+
+ describe "with no target" do
+ let(:cask) { Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/with-generic-artifact-no-target.rb") }
+
+ it "fails to install with no target" do
+ expect(install_phase).to raise_error(Hbc::CaskInvalidError)
+ end
+ end
+
+ it "moves the artifact to the proper directory" do
+ shutup do
+ install_phase.call
+ end
+
+ expect(target_path).to be_a_directory
+ expect(source_path).not_to exist
+ end
+
+ it "avoids clobbering an existing artifact" do
+ target_path.mkpath
+
+ expect {
+ shutup do
+ install_phase.call
+ end
+ }.to raise_error(Hbc::CaskError)
+
+ expect(source_path).to be_a_directory
+ expect(target_path).to be_a_directory
+ expect(File.identical?(source_path, target_path)).to be false
+ end
+end
diff --git a/Library/Homebrew/test/cask/artifact/nested_container_spec.rb b/Library/Homebrew/test/cask/artifact/nested_container_spec.rb
new file mode 100644
index 000000000..3e9a549ea
--- /dev/null
+++ b/Library/Homebrew/test/cask/artifact/nested_container_spec.rb
@@ -0,0 +1,15 @@
+describe Hbc::Artifact::NestedContainer, :cask do
+ describe "install" do
+ it "extracts the specified paths as containers" do
+ cask = Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/nested-app.rb").tap do |c|
+ InstallHelper.install_without_artifacts(c)
+ end
+
+ shutup do
+ Hbc::Artifact::NestedContainer.new(cask).install_phase
+ end
+
+ expect(cask.staged_path.join("MyNestedApp.app")).to be_a_directory
+ end
+ end
+end
diff --git a/Library/Homebrew/test/cask/artifact/pkg_spec.rb b/Library/Homebrew/test/cask/artifact/pkg_spec.rb
new file mode 100644
index 000000000..249439900
--- /dev/null
+++ b/Library/Homebrew/test/cask/artifact/pkg_spec.rb
@@ -0,0 +1,69 @@
+describe Hbc::Artifact::Pkg, :cask do
+ let(:cask) { Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/with-installable.rb") }
+ let(:fake_system_command) { class_double(Hbc::SystemCommand) }
+
+ before(:each) do
+ shutup do
+ InstallHelper.install_without_artifacts(cask)
+ end
+ end
+
+ describe "install_phase" do
+ it "runs the system installer on the specified pkgs" do
+ pkg = Hbc::Artifact::Pkg.new(cask, command: fake_system_command)
+
+ expect(fake_system_command).to receive(:run!).with(
+ "/usr/sbin/installer",
+ args: ["-pkg", cask.staged_path.join("MyFancyPkg", "Fancy.pkg"), "-target", "/"],
+ sudo: true,
+ print_stdout: true,
+ )
+
+ shutup do
+ pkg.install_phase
+ end
+ end
+ end
+
+ describe "choices" do
+ let(:cask) { Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/with-choices.rb") }
+
+ it "passes the choice changes xml to the system installer" do
+ pkg = Hbc::Artifact::Pkg.new(cask, command: fake_system_command)
+
+ file = double(path: Pathname.new("/tmp/choices.xml"))
+
+ expect(file).to receive(:write).with(<<-EOS.undent)
+ <?xml version="1.0" encoding="UTF-8"?>
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+ <plist version="1.0">
+ <array>
+ \t<dict>
+ \t\t<key>attributeSetting</key>
+ \t\t<integer>1</integer>
+ \t\t<key>choiceAttribute</key>
+ \t\t<string>selected</string>
+ \t\t<key>choiceIdentifier</key>
+ \t\t<string>choice1</string>
+ \t</dict>
+ </array>
+ </plist>
+ EOS
+
+ expect(file).to receive(:close)
+ expect(file).to receive(:unlink)
+ expect(Tempfile).to receive(:open).and_yield(file)
+
+ expect(fake_system_command).to receive(:run!).with(
+ "/usr/sbin/installer",
+ args: ["-pkg", cask.staged_path.join("MyFancyPkg", "Fancy.pkg"), "-target", "/", "-applyChoiceChangesXML", cask.staged_path.join("/tmp/choices.xml")],
+ sudo: true,
+ print_stdout: true,
+ )
+
+ shutup do
+ pkg.install_phase
+ end
+ end
+ end
+end
diff --git a/Library/Homebrew/test/cask/artifact/postflight_block_spec.rb b/Library/Homebrew/test/cask/artifact/postflight_block_spec.rb
new file mode 100644
index 000000000..51b1431f0
--- /dev/null
+++ b/Library/Homebrew/test/cask/artifact/postflight_block_spec.rb
@@ -0,0 +1,39 @@
+describe Hbc::Artifact::PostflightBlock, :cask do
+ describe "install_phase" do
+ it "calls the specified block after installing, passing a Cask mini-dsl" do
+ called = false
+ yielded_arg = nil
+
+ cask = Hbc::Cask.new("with-postflight") do
+ postflight do |c|
+ called = true
+ yielded_arg = c
+ end
+ end
+
+ described_class.new(cask).install_phase
+
+ expect(called).to be true
+ expect(yielded_arg).to be_kind_of(Hbc::DSL::Postflight)
+ end
+ end
+
+ describe "uninstall_phase" do
+ it "calls the specified block after uninstalling, passing a Cask mini-dsl" do
+ called = false
+ yielded_arg = nil
+
+ cask = Hbc::Cask.new("with-uninstall-postflight") do
+ uninstall_postflight do |c|
+ called = true
+ yielded_arg = c
+ end
+ end
+
+ described_class.new(cask).uninstall_phase
+
+ expect(called).to be true
+ expect(yielded_arg).to be_kind_of(Hbc::DSL::UninstallPostflight)
+ end
+ end
+end
diff --git a/Library/Homebrew/test/cask/artifact/preflight_block_spec.rb b/Library/Homebrew/test/cask/artifact/preflight_block_spec.rb
new file mode 100644
index 000000000..b13c4ab9d
--- /dev/null
+++ b/Library/Homebrew/test/cask/artifact/preflight_block_spec.rb
@@ -0,0 +1,39 @@
+describe Hbc::Artifact::PreflightBlock, :cask do
+ describe "install_phase" do
+ it "calls the specified block before installing, passing a Cask mini-dsl" do
+ called = false
+ yielded_arg = nil
+
+ cask = Hbc::Cask.new("with-preflight") do
+ preflight do |c|
+ called = true
+ yielded_arg = c
+ end
+ end
+
+ described_class.new(cask).install_phase
+
+ expect(called).to be true
+ expect(yielded_arg).to be_kind_of Hbc::DSL::Preflight
+ end
+ end
+
+ describe "uninstall_phase" do
+ it "calls the specified block before uninstalling, passing a Cask mini-dsl" do
+ called = false
+ yielded_arg = nil
+
+ cask = Hbc::Cask.new("with-uninstall-preflight") do
+ uninstall_preflight do |c|
+ called = true
+ yielded_arg = c
+ end
+ end
+
+ described_class.new(cask).uninstall_phase
+
+ expect(called).to be true
+ expect(yielded_arg).to be_kind_of Hbc::DSL::UninstallPreflight
+ end
+ end
+end
diff --git a/Library/Homebrew/test/cask/artifact/suite_spec.rb b/Library/Homebrew/test/cask/artifact/suite_spec.rb
new file mode 100644
index 000000000..98ae93311
--- /dev/null
+++ b/Library/Homebrew/test/cask/artifact/suite_spec.rb
@@ -0,0 +1,47 @@
+describe Hbc::Artifact::Suite, :cask do
+ let(:cask) { Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/with-suite.rb") }
+
+ let(:install_phase) { -> { Hbc::Artifact::Suite.new(cask).install_phase } }
+
+ let(:target_path) { Hbc.appdir.join("Caffeine") }
+ let(:source_path) { cask.staged_path.join("Caffeine") }
+
+ before(:each) do
+ InstallHelper.install_without_artifacts(cask)
+ end
+
+ it "moves the suite to the proper directory" do
+ skip("flaky test") # FIXME
+
+ shutup do
+ install_phase.call
+ end
+
+ expect(target_path).to be_a_directory
+ expect(target_path).to be_a_symlink
+ expect(target_path.readlink).to exist
+ expect(source_path).not_to exist
+ end
+
+ it "creates a suite containing the expected app" do
+ shutup do
+ install_phase.call
+ end
+
+ expect(target_path.join("Caffeine.app")).to exist
+ end
+
+ it "avoids clobbering an existing suite by moving over it" do
+ target_path.mkpath
+
+ expect {
+ shutup do
+ install_phase.call
+ end
+ }.to raise_error(Hbc::CaskError)
+
+ expect(source_path).to be_a_directory
+ expect(target_path).to be_a_directory
+ expect(File.identical?(source_path, target_path)).to be false
+ end
+end
diff --git a/Library/Homebrew/test/cask/artifact/two_apps_correct_spec.rb b/Library/Homebrew/test/cask/artifact/two_apps_correct_spec.rb
new file mode 100644
index 000000000..9db22b2a3
--- /dev/null
+++ b/Library/Homebrew/test/cask/artifact/two_apps_correct_spec.rb
@@ -0,0 +1,91 @@
+describe Hbc::Artifact::App, :cask do
+ describe "multiple apps" do
+ let(:cask) { Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/with-two-apps-correct.rb") }
+
+ let(:install_phase) {
+ -> { Hbc::Artifact::App.new(cask).install_phase }
+ }
+
+ let(:source_path_mini) { cask.staged_path.join("Caffeine Mini.app") }
+ let(:target_path_mini) { Hbc.appdir.join("Caffeine Mini.app") }
+
+ let(:source_path_pro) { cask.staged_path.join("Caffeine Pro.app") }
+ let(:target_path_pro) { Hbc.appdir.join("Caffeine Pro.app") }
+
+ before(:each) do
+ InstallHelper.install_without_artifacts(cask)
+ end
+
+ it "installs both apps using the proper target directory" do
+ shutup do
+ install_phase.call
+ end
+
+ expect(target_path_mini).to be_a_directory
+ expect(source_path_mini).not_to exist
+
+ expect(target_path_pro).to be_a_directory
+ expect(source_path_pro).not_to exist
+ end
+
+ describe "when apps are in a subdirectory" do
+ let(:cask) { Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/with-two-apps-subdir.rb") }
+
+ it "installs both apps using the proper target directory" do
+ shutup do
+ install_phase.call
+ end
+
+ expect(target_path_mini).to be_a_directory
+ expect(source_path_mini).not_to exist
+
+ expect(target_path_pro).to be_a_directory
+ expect(source_path_pro).not_to exist
+ end
+ end
+
+ it "only uses apps when they are specified" do
+ FileUtils.cp_r source_path_mini, source_path_mini.sub("Caffeine Mini.app", "Caffeine Deluxe.app")
+
+ shutup do
+ install_phase.call
+ end
+
+ expect(target_path_mini).to be_a_directory
+ expect(source_path_mini).not_to exist
+
+ expect(Hbc.appdir.join("Caffeine Deluxe.app")).not_to exist
+ expect(cask.staged_path.join("Caffeine Deluxe.app")).to exist
+ end
+
+ describe "avoids clobbering an existing app" do
+ it "when the first app of two already exists" do
+ target_path_mini.mkpath
+
+ expect {
+ expect(install_phase).to output(<<-EOS.undent).to_stdout
+ ==> Moving App 'Caffeine Pro.app' to '#{target_path_pro}'
+ EOS
+ }.to raise_error(Hbc::CaskError, "It seems there is already an App at '#{target_path_mini}'.")
+
+ expect(source_path_mini).to be_a_directory
+ expect(target_path_mini).to be_a_directory
+ expect(File.identical?(source_path_mini, target_path_mini)).to be false
+ end
+
+ it "when the second app of two already exists" do
+ target_path_pro.mkpath
+
+ expect {
+ expect(install_phase).to output(<<-EOS.undent).to_stdout
+ ==> Moving App 'Caffeine Mini.app' to '#{target_path_mini}'
+ EOS
+ }.to raise_error(Hbc::CaskError, "It seems there is already an App at '#{target_path_pro}'.")
+
+ expect(source_path_pro).to be_a_directory
+ expect(target_path_pro).to be_a_directory
+ expect(File.identical?(source_path_pro, target_path_pro)).to be false
+ end
+ end
+ end
+end
diff --git a/Library/Homebrew/test/cask/artifact/two_apps_incorrect_spec.rb b/Library/Homebrew/test/cask/artifact/two_apps_incorrect_spec.rb
new file mode 100644
index 000000000..6427ec32c
--- /dev/null
+++ b/Library/Homebrew/test/cask/artifact/two_apps_incorrect_spec.rb
@@ -0,0 +1,9 @@
+describe Hbc::Artifact::App, :cask do
+ # FIXME: Doesn't actually raise because the `app` stanza is not evaluated on load.
+ # it "must raise" do
+ # lambda {
+ # Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/with-two-apps-incorrect.rb")
+ # }.must_raise
+ # # TODO: later give the user a nice exception for this case and check for it here
+ # end
+end
diff --git a/Library/Homebrew/test/cask/artifact/uninstall_no_zap_spec.rb b/Library/Homebrew/test/cask/artifact/uninstall_no_zap_spec.rb
new file mode 100644
index 000000000..f88aaa49d
--- /dev/null
+++ b/Library/Homebrew/test/cask/artifact/uninstall_no_zap_spec.rb
@@ -0,0 +1,19 @@
+describe Hbc::Artifact::Zap, :cask do
+ let(:cask) { Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/with-installable.rb") }
+
+ let(:zap_artifact) {
+ Hbc::Artifact::Zap.new(cask)
+ }
+
+ before do
+ shutup do
+ InstallHelper.install_without_artifacts(cask)
+ end
+ end
+
+ describe "#uninstall_phase" do
+ subject { zap_artifact }
+
+ it { is_expected.not_to respond_to(:uninstall_phase) }
+ end
+end
diff --git a/Library/Homebrew/test/cask/artifact/uninstall_spec.rb b/Library/Homebrew/test/cask/artifact/uninstall_spec.rb
new file mode 100644
index 000000000..b7deb4575
--- /dev/null
+++ b/Library/Homebrew/test/cask/artifact/uninstall_spec.rb
@@ -0,0 +1,351 @@
+describe Hbc::Artifact::Uninstall, :cask do
+ let(:cask) { Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/with-installable.rb") }
+
+ let(:uninstall_artifact) {
+ Hbc::Artifact::Uninstall.new(cask, command: Hbc::FakeSystemCommand)
+ }
+
+ let(:dir) { TEST_TMPDIR }
+ let(:absolute_path) { Pathname.new("#{dir}/absolute_path") }
+ let(:path_with_tilde) { Pathname.new("#{dir}/path_with_tilde") }
+ let(:glob_path1) { Pathname.new("#{dir}/glob_path1") }
+ let(:glob_path2) { Pathname.new("#{dir}/glob_path2") }
+
+ around(:each) do |example|
+ begin
+ ENV["HOME"] = dir
+
+ paths = [
+ absolute_path,
+ path_with_tilde,
+ glob_path1,
+ glob_path2,
+ ]
+
+ FileUtils.touch paths
+
+ shutup do
+ InstallHelper.install_without_artifacts(cask)
+ end
+
+ example.run
+ ensure
+ FileUtils.rm_f paths
+ end
+ end
+
+ describe "uninstall_phase" do
+ subject {
+ shutup do
+ uninstall_artifact.uninstall_phase
+ end
+ }
+
+ context "when using launchctl" do
+ let(:cask) { Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/with-uninstall-launchctl.rb") }
+ let(:launchctl_list_cmd) { %w[/bin/launchctl list my.fancy.package.service] }
+ let(:launchctl_remove_cmd) { %w[/bin/launchctl remove my.fancy.package.service] }
+ let(:unknown_response) { "launchctl list returned unknown response\n" }
+ let(:service_info) {
+ <<-EOS.undent
+ {
+ "LimitLoadToSessionType" = "Aqua";
+ "Label" = "my.fancy.package.service";
+ "TimeOut" = 30;
+ "OnDemand" = true;
+ "LastExitStatus" = 0;
+ "ProgramArguments" = (
+ "argument";
+ );
+ };
+ EOS
+ }
+
+ context "when launchctl job is owned by user" do
+ it "can uninstall" do
+ Hbc::FakeSystemCommand.stubs_command(
+ launchctl_list_cmd,
+ service_info,
+ )
+
+ Hbc::FakeSystemCommand.stubs_command(
+ sudo(launchctl_list_cmd),
+ unknown_response,
+ )
+
+ Hbc::FakeSystemCommand.expects_command(launchctl_remove_cmd)
+
+ subject
+ end
+ end
+
+ context "when launchctl job is owned by system" do
+ it "can uninstall" do
+ Hbc::FakeSystemCommand.stubs_command(
+ launchctl_list_cmd,
+ unknown_response,
+ )
+
+ Hbc::FakeSystemCommand.stubs_command(
+ sudo(launchctl_list_cmd),
+ service_info,
+ )
+
+ Hbc::FakeSystemCommand.expects_command(sudo(launchctl_remove_cmd))
+
+ subject
+ end
+ end
+ end
+
+ context "when using pkgutil" do
+ let(:cask) { Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/with-uninstall-pkgutil.rb") }
+ let(:main_pkg_id) { "my.fancy.package.main" }
+ let(:agent_pkg_id) { "my.fancy.package.agent" }
+ let(:main_files) {
+ %w[
+ fancy/bin/fancy.exe
+ fancy/var/fancy.data
+ ]
+ }
+ let(:main_dirs) {
+ %w[
+ fancy
+ fancy/bin
+ fancy/var
+ ]
+ }
+ let(:agent_files) {
+ %w[
+ fancy/agent/fancy-agent.exe
+ fancy/agent/fancy-agent.pid
+ fancy/agent/fancy-agent.log
+ ]
+ }
+ let(:agent_dirs) {
+ %w[
+ fancy
+ fancy/agent
+ ]
+ }
+ let(:pkg_info_plist) {
+ <<-EOS.undent
+ <?xml version="1.0" encoding="UTF-8"?>
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+ <plist version="1.0">
+ <dict>
+ <key>install-location</key>
+ <string>tmp</string>
+ <key>volume</key>
+ <string>/</string>
+ </dict>
+ </plist>
+ EOS
+ }
+
+ it "can uninstall" do
+ Hbc::FakeSystemCommand.stubs_command(
+ %w[/usr/sbin/pkgutil --pkgs=my.fancy.package.*],
+ "#{main_pkg_id}\n#{agent_pkg_id}",
+ )
+
+ [
+ [main_pkg_id, main_files, main_dirs],
+ [agent_pkg_id, agent_files, agent_dirs],
+ ].each do |pkg_id, pkg_files, pkg_dirs|
+ Hbc::FakeSystemCommand.stubs_command(
+ %W[/usr/sbin/pkgutil --only-files --files #{pkg_id}],
+ pkg_files.join("\n"),
+ )
+
+ Hbc::FakeSystemCommand.stubs_command(
+ %W[/usr/sbin/pkgutil --only-dirs --files #{pkg_id}],
+ pkg_dirs.join("\n"),
+ )
+
+ Hbc::FakeSystemCommand.stubs_command(
+ %W[/usr/sbin/pkgutil --files #{pkg_id}],
+ (pkg_files + pkg_dirs).join("\n"),
+ )
+
+ Hbc::FakeSystemCommand.stubs_command(
+ %W[/usr/sbin/pkgutil --pkg-info-plist #{pkg_id}],
+ pkg_info_plist,
+ )
+
+ Hbc::FakeSystemCommand.expects_command(sudo(%W[/usr/sbin/pkgutil --forget #{pkg_id}]))
+
+ Hbc::FakeSystemCommand.expects_command(
+ sudo(%w[/bin/rm -f --] + pkg_files.map { |path| Pathname("/tmp/#{path}") }),
+ )
+ end
+
+ subject
+ end
+ end
+
+ context "when using kext" do
+ let(:cask) { Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/with-uninstall-kext.rb") }
+ let(:kext_id) { "my.fancy.package.kernelextension" }
+
+ it "can uninstall" do
+ Hbc::FakeSystemCommand.stubs_command(
+ sudo(%W[/usr/sbin/kextstat -l -b #{kext_id}]), "loaded"
+ )
+
+ Hbc::FakeSystemCommand.expects_command(
+ sudo(%W[/sbin/kextunload -b #{kext_id}]),
+ )
+
+ Hbc::FakeSystemCommand.expects_command(
+ sudo(%W[/usr/sbin/kextfind -b #{kext_id}]), "/Library/Extensions/FancyPackage.kext\n"
+ )
+
+ Hbc::FakeSystemCommand.expects_command(
+ sudo(["/bin/rm", "-rf", "/Library/Extensions/FancyPackage.kext"]),
+ )
+
+ subject
+ end
+ end
+
+ context "when using quit" do
+ let(:cask) { Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/with-uninstall-quit.rb") }
+ let(:bundle_id) { "my.fancy.package.app" }
+ let(:quit_application_script) {
+ %Q(tell application id "#{bundle_id}" to quit)
+ }
+
+ it "can uninstall" do
+ Hbc::FakeSystemCommand.stubs_command(
+ %w[/bin/launchctl list], "999\t0\t#{bundle_id}\n"
+ )
+
+ Hbc::FakeSystemCommand.stubs_command(
+ %w[/bin/launchctl list],
+ )
+
+ subject
+ end
+ end
+
+ context "when using signal" do
+ let(:cask) { Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/with-uninstall-signal.rb") }
+ let(:bundle_id) { "my.fancy.package.app" }
+ let(:signals) { %w[TERM KILL] }
+ let(:unix_pids) { [12_345, 67_890] }
+
+ it "can uninstall" do
+ Hbc::FakeSystemCommand.stubs_command(
+ %w[/bin/launchctl list], unix_pids.map { |pid| [pid, 0, bundle_id].join("\t") }.join("\n")
+ )
+
+ signals.each do |signal|
+ expect(Process).to receive(:kill).with(signal, *unix_pids)
+ end
+
+ subject
+ end
+ end
+
+ context "when using delete" do
+ let(:cask) { Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/with-uninstall-delete.rb") }
+
+ it "can uninstall" do
+ Hbc::FakeSystemCommand.expects_command(
+ sudo(%w[/bin/rm -rf --],
+ absolute_path,
+ path_with_tilde,
+ glob_path1,
+ glob_path2),
+ )
+
+ subject
+ end
+ end
+
+ context "when using trash" do
+ let(:cask) { Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/with-uninstall-trash.rb") }
+
+ it "can uninstall" do
+ Hbc::FakeSystemCommand.expects_command(
+ sudo(%w[/bin/rm -rf --],
+ absolute_path,
+ path_with_tilde,
+ glob_path1,
+ glob_path2),
+ )
+
+ subject
+ end
+ end
+
+ context "when using rmdir" do
+ let(:cask) { Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/with-uninstall-rmdir.rb") }
+ let(:empty_directory_path) { Pathname.new("#{TEST_TMPDIR}/empty_directory_path") }
+
+ before(:each) do
+ empty_directory_path.mkdir
+ end
+
+ after(:each) do
+ empty_directory_path.rmdir
+ end
+
+ it "can uninstall" do
+ Hbc::FakeSystemCommand.expects_command(
+ sudo(%w[/bin/rm -f --], empty_directory_path/".DS_Store"),
+ )
+
+ Hbc::FakeSystemCommand.expects_command(
+ sudo(%w[/bin/rmdir --], empty_directory_path),
+ )
+
+ subject
+ end
+ end
+
+ context "when using script" do
+ let(:cask) { Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/with-uninstall-script.rb") }
+ let(:script_pathname) { cask.staged_path.join("MyFancyPkg", "FancyUninstaller.tool") }
+
+ it "can uninstall" do
+ Hbc::FakeSystemCommand.expects_command(%w[/bin/chmod -- +x] + [script_pathname])
+
+ Hbc::FakeSystemCommand.expects_command(
+ sudo(cask.staged_path.join("MyFancyPkg", "FancyUninstaller.tool"), "--please"),
+ )
+
+ subject
+ end
+ end
+
+ context "when using early_script" do
+ let(:cask) { Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/with-uninstall-early-script.rb") }
+ let(:script_pathname) { cask.staged_path.join("MyFancyPkg", "FancyUninstaller.tool") }
+
+ it "can uninstall" do
+ Hbc::FakeSystemCommand.expects_command(%w[/bin/chmod -- +x] + [script_pathname])
+
+ Hbc::FakeSystemCommand.expects_command(
+ sudo(cask.staged_path.join("MyFancyPkg", "FancyUninstaller.tool"), "--please"),
+ )
+
+ subject
+ end
+ end
+
+ context "when using login_item" do
+ let(:cask) { Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/with-uninstall-login-item.rb") }
+
+ it "can uninstall" do
+ Hbc::FakeSystemCommand.expects_command(
+ ["/usr/bin/osascript", "-e", 'tell application "System Events" to delete every login ' \
+ 'item whose name is "Fancy"'],
+ )
+
+ subject
+ end
+ end
+ end
+end
diff --git a/Library/Homebrew/test/cask/artifact/zap_spec.rb b/Library/Homebrew/test/cask/artifact/zap_spec.rb
new file mode 100644
index 000000000..fdf2e4f9d
--- /dev/null
+++ b/Library/Homebrew/test/cask/artifact/zap_spec.rb
@@ -0,0 +1,352 @@
+# TODO: test that zap removes an alternate version of the same Cask
+describe Hbc::Artifact::Zap, :cask do
+ let(:cask) { Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/with-installable.rb") }
+
+ let(:zap_artifact) {
+ Hbc::Artifact::Zap.new(cask, command: Hbc::FakeSystemCommand)
+ }
+
+ let(:dir) { TEST_TMPDIR }
+ let(:absolute_path) { Pathname.new("#{dir}/absolute_path") }
+ let(:path_with_tilde) { Pathname.new("#{dir}/path_with_tilde") }
+ let(:glob_path1) { Pathname.new("#{dir}/glob_path1") }
+ let(:glob_path2) { Pathname.new("#{dir}/glob_path2") }
+
+ around(:each) do |example|
+ begin
+ ENV["HOME"] = dir
+
+ paths = [
+ absolute_path,
+ path_with_tilde,
+ glob_path1,
+ glob_path2,
+ ]
+
+ FileUtils.touch paths
+
+ shutup do
+ InstallHelper.install_without_artifacts(cask)
+ end
+
+ example.run
+ ensure
+ FileUtils.rm_f paths
+ end
+ end
+
+ describe "#zap_phase" do
+ subject {
+ shutup do
+ zap_artifact.zap_phase
+ end
+ }
+
+ context "when using launchctl" do
+ let(:cask) { Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/with-zap-launchctl.rb") }
+ let(:launchctl_list_cmd) { %w[/bin/launchctl list my.fancy.package.service] }
+ let(:launchctl_remove_cmd) { %w[/bin/launchctl remove my.fancy.package.service] }
+ let(:unknown_response) { "launchctl list returned unknown response\n" }
+ let(:service_info) {
+ <<-EOS.undent
+ {
+ "LimitLoadToSessionType" = "Aqua";
+ "Label" = "my.fancy.package.service";
+ "TimeOut" = 30;
+ "OnDemand" = true;
+ "LastExitStatus" = 0;
+ "ProgramArguments" = (
+ "argument";
+ );
+ };
+ EOS
+ }
+
+ context "when launchctl job is owned by user" do
+ it "can zap" do
+ Hbc::FakeSystemCommand.stubs_command(
+ launchctl_list_cmd,
+ service_info,
+ )
+
+ Hbc::FakeSystemCommand.stubs_command(
+ sudo(launchctl_list_cmd),
+ unknown_response,
+ )
+
+ Hbc::FakeSystemCommand.expects_command(launchctl_remove_cmd)
+
+ subject
+ end
+ end
+
+ describe "when launchctl job is owned by system" do
+ it "can zap" do
+ Hbc::FakeSystemCommand.stubs_command(
+ launchctl_list_cmd,
+ unknown_response,
+ )
+
+ Hbc::FakeSystemCommand.stubs_command(
+ sudo(launchctl_list_cmd),
+ service_info,
+ )
+
+ Hbc::FakeSystemCommand.expects_command(sudo(launchctl_remove_cmd))
+
+ subject
+ end
+ end
+ end
+
+ context "when using pkgutil" do
+ let(:cask) { Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/with-zap-pkgutil.rb") }
+ let(:main_pkg_id) { "my.fancy.package.main" }
+ let(:agent_pkg_id) { "my.fancy.package.agent" }
+ let(:main_files) {
+ %w[
+ fancy/bin/fancy.exe
+ fancy/var/fancy.data
+ ]
+ }
+ let(:main_dirs) {
+ %w[
+ fancy
+ fancy/bin
+ fancy/var
+ ]
+ }
+ let(:agent_files) {
+ %w[
+ fancy/agent/fancy-agent.exe
+ fancy/agent/fancy-agent.pid
+ fancy/agent/fancy-agent.log
+ ]
+ }
+ let(:agent_dirs) {
+ %w[
+ fancy
+ fancy/agent
+ ]
+ }
+ let(:pkg_info_plist) {
+ <<-EOS.undent
+ <?xml version="1.0" encoding="UTF-8"?>
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+ <plist version="1.0">
+ <dict>
+ <key>install-location</key>
+ <string>tmp</string>
+ <key>volume</key>
+ <string>/</string>
+ </dict>
+ </plist>
+ EOS
+ }
+
+ it "can zap" do
+ Hbc::FakeSystemCommand.stubs_command(
+ %w[/usr/sbin/pkgutil --pkgs=my.fancy.package.*],
+ "#{main_pkg_id}\n#{agent_pkg_id}",
+ )
+
+ [
+ [main_pkg_id, main_files, main_dirs],
+ [agent_pkg_id, agent_files, agent_dirs],
+ ].each do |pkg_id, pkg_files, pkg_dirs|
+ Hbc::FakeSystemCommand.stubs_command(
+ %W[/usr/sbin/pkgutil --only-files --files #{pkg_id}],
+ pkg_files.join("\n"),
+ )
+
+ Hbc::FakeSystemCommand.stubs_command(
+ %W[/usr/sbin/pkgutil --only-dirs --files #{pkg_id}],
+ pkg_dirs.join("\n"),
+ )
+
+ Hbc::FakeSystemCommand.stubs_command(
+ %W[/usr/sbin/pkgutil --files #{pkg_id}],
+ (pkg_files + pkg_dirs).join("\n"),
+ )
+
+ Hbc::FakeSystemCommand.stubs_command(
+ %W[/usr/sbin/pkgutil --pkg-info-plist #{pkg_id}],
+ pkg_info_plist,
+ )
+
+ Hbc::FakeSystemCommand.expects_command(sudo(%W[/usr/sbin/pkgutil --forget #{pkg_id}]))
+
+ Hbc::FakeSystemCommand.expects_command(
+ sudo(%w[/bin/rm -f --] + pkg_files.map { |path| Pathname("/tmp/#{path}") }),
+ )
+ end
+
+ subject
+ end
+ end
+
+ context "when using kext" do
+ let(:cask) { Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/with-zap-kext.rb") }
+ let(:kext_id) { "my.fancy.package.kernelextension" }
+
+ it "can zap" do
+ Hbc::FakeSystemCommand.stubs_command(
+ sudo(%W[/usr/sbin/kextstat -l -b #{kext_id}]), "loaded"
+ )
+
+ Hbc::FakeSystemCommand.expects_command(
+ sudo(%W[/sbin/kextunload -b #{kext_id}]),
+ )
+
+ Hbc::FakeSystemCommand.expects_command(
+ sudo(%W[/usr/sbin/kextfind -b #{kext_id}]), "/Library/Extensions/FancyPackage.kext\n"
+ )
+
+ Hbc::FakeSystemCommand.expects_command(
+ sudo(["/bin/rm", "-rf", "/Library/Extensions/FancyPackage.kext"]),
+ )
+
+ subject
+ end
+ end
+
+ context "when using quit" do
+ let(:cask) { Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/with-zap-quit.rb") }
+ let(:bundle_id) { "my.fancy.package.app" }
+ let(:quit_application_script) {
+ %Q(tell application id "#{bundle_id}" to quit)
+ }
+
+ it "can zap" do
+ Hbc::FakeSystemCommand.stubs_command(
+ %w[/bin/launchctl list], "999\t0\t#{bundle_id}\n"
+ )
+
+ Hbc::FakeSystemCommand.stubs_command(
+ %w[/bin/launchctl list],
+ )
+
+ subject
+ end
+ end
+
+ context "when using signal" do
+ let(:cask) { Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/with-zap-signal.rb") }
+ let(:bundle_id) { "my.fancy.package.app" }
+ let(:signals) { %w[TERM KILL] }
+ let(:unix_pids) { [12_345, 67_890] }
+
+ it "can zap" do
+ Hbc::FakeSystemCommand.stubs_command(
+ %w[/bin/launchctl list], unix_pids.map { |pid| [pid, 0, bundle_id].join("\t") }.join("\n")
+ )
+
+ signals.each do |signal|
+ expect(Process).to receive(:kill).with(signal, *unix_pids)
+ end
+
+ subject
+ end
+ end
+
+ context "when using delete" do
+ let(:cask) { Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/with-zap-delete.rb") }
+
+ it "can zap" do
+ Hbc::FakeSystemCommand.expects_command(
+ sudo(%w[/bin/rm -rf --],
+ absolute_path,
+ path_with_tilde,
+ glob_path1,
+ glob_path2),
+ )
+
+ subject
+ end
+ end
+
+ context "when using trash" do
+ let(:cask) { Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/with-zap-trash.rb") }
+
+ it "can zap" do
+ Hbc::FakeSystemCommand.expects_command(
+ sudo(%w[/bin/rm -rf --],
+ absolute_path,
+ path_with_tilde,
+ glob_path1,
+ glob_path2),
+ )
+
+ subject
+ end
+ end
+
+ context "when using rmdir" do
+ let(:cask) { Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/with-zap-rmdir.rb") }
+ let(:empty_directory_path) { Pathname.new("#{TEST_TMPDIR}/empty_directory_path") }
+
+ before(:each) do
+ empty_directory_path.mkdir
+ end
+
+ after(:each) do
+ empty_directory_path.rmdir
+ end
+
+ it "can zap" do
+ Hbc::FakeSystemCommand.expects_command(
+ sudo(%w[/bin/rm -f --], empty_directory_path/".DS_Store"),
+ )
+
+ Hbc::FakeSystemCommand.expects_command(
+ sudo(%w[/bin/rmdir --], empty_directory_path),
+ )
+
+ subject
+ end
+ end
+
+ context "when using script" do
+ let(:cask) { Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/with-zap-script.rb") }
+ let(:script_pathname) { cask.staged_path.join("MyFancyPkg", "FancyUninstaller.tool") }
+
+ it "can zap" do
+ Hbc::FakeSystemCommand.expects_command(%w[/bin/chmod -- +x] + [script_pathname])
+
+ Hbc::FakeSystemCommand.expects_command(
+ sudo(cask.staged_path.join("MyFancyPkg", "FancyUninstaller.tool"), "--please"),
+ )
+
+ subject
+ end
+ end
+
+ context "when using early_script" do
+ let(:cask) { Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/with-zap-early-script.rb") }
+ let(:script_pathname) { cask.staged_path.join("MyFancyPkg", "FancyUninstaller.tool") }
+
+ it "can zap" do
+ Hbc::FakeSystemCommand.expects_command(%w[/bin/chmod -- +x] + [script_pathname])
+
+ Hbc::FakeSystemCommand.expects_command(
+ sudo(cask.staged_path.join("MyFancyPkg", "FancyUninstaller.tool"), "--please"),
+ )
+
+ subject
+ end
+ end
+
+ context "when using login_item" do
+ let(:cask) { Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/with-zap-login-item.rb") }
+
+ it "can zap" do
+ Hbc::FakeSystemCommand.expects_command(
+ ["/usr/bin/osascript", "-e", 'tell application "System Events" to delete every login ' \
+ 'item whose name is "Fancy"'],
+ )
+
+ subject
+ end
+ end
+ end
+end
diff --git a/Library/Homebrew/test/cask/audit_spec.rb b/Library/Homebrew/test/cask/audit_spec.rb
new file mode 100644
index 000000000..802807fcb
--- /dev/null
+++ b/Library/Homebrew/test/cask/audit_spec.rb
@@ -0,0 +1,364 @@
+describe Hbc::Audit, :cask do
+ def include_msg?(messages, msg)
+ if msg.is_a?(Regexp)
+ Array(messages).any? { |m| m =~ msg }
+ else
+ Array(messages).include?(msg)
+ end
+ end
+
+ matcher :pass do
+ match do |audit|
+ !audit.errors? && !audit.warnings?
+ end
+ end
+
+ matcher :fail do
+ match(&:errors?)
+ end
+
+ matcher :warn do
+ match do |audit|
+ audit.warnings? && !audit.errors?
+ end
+ end
+
+ matcher :fail_with do |error_msg|
+ match do |audit|
+ include_msg?(audit.errors, error_msg)
+ end
+ end
+
+ matcher :warn_with do |warning_msg|
+ match do |audit|
+ include_msg?(audit.warnings, warning_msg)
+ end
+ end
+
+ let(:cask) { instance_double(Hbc::Cask) }
+ let(:download) { false }
+ let(:check_token_conflicts) { false }
+ let(:fake_system_command) { class_double(Hbc::SystemCommand) }
+ let(:audit) {
+ Hbc::Audit.new(cask, download: download,
+ check_token_conflicts: check_token_conflicts,
+ command: fake_system_command)
+ }
+
+ describe "#result" do
+ subject { audit.result }
+
+ context "when there are errors" do
+ before do
+ audit.add_error "bad"
+ end
+
+ it { is_expected.to match(/failed/) }
+ end
+
+ context "when there are warnings" do
+ before do
+ audit.add_warning "eh"
+ end
+
+ it { is_expected.to match(/warning/) }
+ end
+
+ context "when there are errors and warnings" do
+ before do
+ audit.add_error "bad"
+ audit.add_warning "eh"
+ end
+
+ it { is_expected.to match(/failed/) }
+ end
+
+ context "when there are no errors or warnings" do
+ it { is_expected.to match(/passed/) }
+ end
+ end
+
+ describe "#run!" do
+ let(:cask) { Hbc.load(cask_token) }
+ subject { audit.run! }
+
+ describe "required stanzas" do
+ %w[version sha256 url name homepage].each do |stanza|
+ context "when missing #{stanza}" do
+ let(:cask_token) { "missing-#{stanza}" }
+ it { is_expected.to fail_with(/#{stanza} stanza is required/) }
+ end
+ end
+ end
+
+ describe "version checks" do
+ let(:error_msg) { "you should use version :latest instead of version 'latest'" }
+
+ context "when version is 'latest'" do
+ let(:cask_token) { "version-latest-string" }
+ it { is_expected.to fail_with(error_msg) }
+ end
+
+ context "when version is :latest" do
+ let(:cask_token) { "version-latest-with-checksum" }
+ it { should_not fail_with(error_msg) }
+ end
+ end
+
+ describe "sha256 checks" do
+ context "when version is :latest and sha256 is not :no_check" do
+ let(:cask_token) { "version-latest-with-checksum" }
+ it { is_expected.to fail_with("you should use sha256 :no_check when version is :latest") }
+ end
+
+ context "when sha256 is not a legal SHA-256 digest" do
+ let(:cask_token) { "invalid-sha256" }
+ it { is_expected.to fail_with("sha256 string must be of 64 hexadecimal characters") }
+ end
+
+ context "when sha256 is sha256 for empty string" do
+ let(:cask_token) { "sha256-for-empty-string" }
+ it { is_expected.to fail_with(/cannot use the sha256 for an empty string/) }
+ end
+ end
+
+ describe "appcast checks" do
+ context "when appcast has no sha256" do
+ let(:cask_token) { "appcast-missing-checkpoint" }
+ it { is_expected.to fail_with(/checkpoint sha256 is required for appcast/) }
+ end
+
+ context "when appcast checkpoint is not a string of 64 hexadecimal characters" do
+ let(:cask_token) { "appcast-invalid-checkpoint" }
+ it { is_expected.to fail_with(/string must be of 64 hexadecimal characters/) }
+ end
+
+ context "when appcast checkpoint is sha256 for empty string" do
+ let(:cask_token) { "appcast-checkpoint-sha256-for-empty-string" }
+ it { is_expected.to fail_with(/cannot use the sha256 for an empty string/) }
+ end
+
+ context "when appcast checkpoint is valid sha256" do
+ let(:cask_token) { "appcast-valid-checkpoint" }
+ it { should_not fail_with(/appcast :checkpoint/) }
+ end
+
+ context "when verifying appcast HTTP code" do
+ let(:cask_token) { "appcast-valid-checkpoint" }
+ let(:download) { instance_double(Hbc::Download) }
+ let(:wrong_code_msg) { /unexpected HTTP response code/ }
+ let(:curl_error_msg) { /error retrieving appcast/ }
+ let(:fake_curl_result) { instance_double(Hbc::SystemCommand::Result) }
+
+ before do
+ allow(audit).to receive(:check_appcast_checkpoint_accuracy)
+ allow(fake_system_command).to receive(:run).and_return(fake_curl_result)
+ allow(fake_curl_result).to receive(:success?).and_return(success)
+ end
+
+ context "when curl succeeds" do
+ let(:success) { true }
+
+ before do
+ allow(fake_curl_result).to receive(:stdout).and_return(stdout)
+ end
+
+ context "when HTTP code is 200" do
+ let(:stdout) { "200" }
+ it { should_not warn_with(wrong_code_msg) }
+ end
+
+ context "when HTTP code is not 200" do
+ let(:stdout) { "404" }
+ it { is_expected.to warn_with(wrong_code_msg) }
+ end
+ end
+
+ context "when curl fails" do
+ let(:success) { false }
+
+ before do
+ allow(fake_curl_result).to receive(:stderr).and_return("Some curl error")
+ end
+
+ it { is_expected.to warn_with(curl_error_msg) }
+ end
+ end
+
+ context "when verifying appcast checkpoint" do
+ let(:cask_token) { "appcast-valid-checkpoint" }
+ let(:download) { instance_double(Hbc::Download) }
+ let(:mismatch_msg) { /appcast checkpoint mismatch/ }
+ let(:curl_error_msg) { /error retrieving appcast/ }
+ let(:fake_curl_result) { instance_double(Hbc::SystemCommand::Result) }
+ let(:expected_checkpoint) { "d5b2dfbef7ea28c25f7a77cd7fa14d013d82b626db1d82e00e25822464ba19e2" }
+
+ before do
+ allow(audit).to receive(:check_appcast_http_code)
+ allow(Hbc::SystemCommand).to receive(:run).and_return(fake_curl_result)
+ allow(fake_curl_result).to receive(:success?).and_return(success)
+ end
+
+ context "when appcast download succeeds" do
+ let(:success) { true }
+ let(:appcast_text) { instance_double(::String) }
+
+ before do
+ allow(fake_curl_result).to receive(:stdout).and_return(appcast_text)
+ allow(appcast_text).to receive(:gsub).and_return(appcast_text)
+ allow(appcast_text).to receive(:end_with?).with("\n").and_return(true)
+ allow(Digest::SHA2).to receive(:hexdigest).and_return(actual_checkpoint)
+ end
+
+ context "when appcast checkpoint is out of date" do
+ let(:actual_checkpoint) { "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef" }
+ it { is_expected.to warn_with(mismatch_msg) }
+ it { should_not warn_with(curl_error_msg) }
+ end
+
+ context "when appcast checkpoint is up to date" do
+ let(:actual_checkpoint) { expected_checkpoint }
+ it { should_not warn_with(mismatch_msg) }
+ it { should_not warn_with(curl_error_msg) }
+ end
+ end
+
+ context "when appcast download fails" do
+ let(:success) { false }
+
+ before do
+ allow(fake_curl_result).to receive(:stderr).and_return("Some curl error")
+ end
+
+ it { is_expected.to warn_with(curl_error_msg) }
+ end
+ end
+ end
+
+ describe "preferred download URL formats" do
+ let(:warning_msg) { /URL format incorrect/ }
+
+ context "with incorrect SourceForge URL format" do
+ let(:cask_token) { "sourceforge-incorrect-url-format" }
+ it { is_expected.to warn_with(warning_msg) }
+ end
+
+ context "with correct SourceForge URL format" do
+ let(:cask_token) { "sourceforge-correct-url-format" }
+ it { should_not warn_with(warning_msg) }
+ end
+
+ context "with correct SourceForge URL format for version :latest" do
+ let(:cask_token) { "sourceforge-version-latest-correct-url-format" }
+ it { should_not warn_with(warning_msg) }
+ end
+
+ context "with incorrect OSDN URL format" do
+ let(:cask_token) { "osdn-incorrect-url-format" }
+ it { is_expected.to warn_with(warning_msg) }
+ end
+
+ context "with correct OSDN URL format" do
+ let(:cask_token) { "osdn-correct-url-format" }
+ it { should_not warn_with(warning_msg) }
+ end
+ end
+
+ describe "generic artifact checks" do
+ context "with no target" do
+ let(:cask_token) { "generic-artifact-no-target" }
+ it { is_expected.to fail_with(/target required for generic artifact/) }
+ end
+
+ context "with relative target" do
+ let(:cask_token) { "generic-artifact-relative-target" }
+ it { is_expected.to fail_with(/target must be absolute path for generic artifact/) }
+ end
+
+ context "with absolute target" do
+ let(:cask_token) { "generic-artifact-absolute-target" }
+ it { should_not fail_with(/target required for generic artifact/) }
+ end
+ end
+
+ describe "url checks" do
+ context "given a block" do
+ let(:cask_token) { "booby-trap" }
+
+ context "when loading the cask" do
+ it "does not evaluate the block" do
+ expect { cask }.not_to raise_error
+ end
+ end
+
+ context "when doing the audit" do
+ it "evaluates the block" do
+ expect(subject).to fail_with(/Boom/)
+ end
+ end
+ end
+ end
+
+ describe "token conflicts" do
+ let(:cask_token) { "with-binary" }
+ let(:check_token_conflicts) { true }
+
+ before do
+ expect(audit).to receive(:core_formula_names).and_return(formula_names)
+ end
+
+ context "when cask token conflicts with a core formula" do
+ let(:formula_names) { %w[with-binary other-formula] }
+ it { is_expected.to warn_with(/possible duplicate/) }
+ end
+
+ context "when cask token does not conflict with a core formula" do
+ let(:formula_names) { %w[other-formula] }
+ it { should_not warn_with(/possible duplicate/) }
+ end
+ end
+
+ describe "audit of downloads" do
+ let(:cask_token) { "with-binary" }
+ let(:cask) { Hbc.load(cask_token) }
+ let(:download) { instance_double(Hbc::Download) }
+ let(:verify) { class_double(Hbc::Verify).as_stubbed_const }
+ let(:error_msg) { "Download Failed" }
+
+ context "when download and verification succeed" do
+ before do
+ expect(download).to receive(:perform)
+ expect(verify).to receive(:all)
+ end
+
+ it { should_not fail_with(/#{error_msg}/) }
+ end
+
+ context "when download fails" do
+ before do
+ expect(download).to receive(:perform).and_raise(StandardError.new(error_msg))
+ end
+
+ it { is_expected.to fail_with(/#{error_msg}/) }
+ end
+
+ context "when verification fails" do
+ before do
+ expect(download).to receive(:perform)
+ expect(verify).to receive(:all).and_raise(StandardError.new(error_msg))
+ end
+
+ it { is_expected.to fail_with(/#{error_msg}/) }
+ end
+ end
+
+ context "when an exception is raised" do
+ let(:cask) { instance_double(Hbc::Cask) }
+ before do
+ expect(cask).to receive(:version).and_raise(StandardError.new)
+ end
+
+ it { is_expected.to fail_with(/exception while auditing/) }
+ end
+ end
+end
diff --git a/Library/Homebrew/test/cask/cask_spec.rb b/Library/Homebrew/test/cask/cask_spec.rb
new file mode 100644
index 000000000..d76f2dce9
--- /dev/null
+++ b/Library/Homebrew/test/cask/cask_spec.rb
@@ -0,0 +1,92 @@
+describe Hbc::Cask, :cask do
+ let(:cask) { described_class.new("versioned-cask") }
+
+ context "when multiple versions are installed" do
+ describe "#versions" do
+ context "and 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"],
+ ])
+
+ expect(cask).to receive(:timestamped_versions)
+ expect(cask.versions).to eq([
+ "1.2.3",
+ "1.2.2",
+ ])
+ end
+ end
+ end
+ end
+
+ describe "load" do
+ let(:tap_path) { Hbc.default_tap.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 = Hbc.load("local-caffeine")
+ expect(c).to be_kind_of(Hbc::Cask)
+ expect(c.token).to eq("local-caffeine")
+ end
+
+ it "returns an instance of the Cask from a specific file location" do
+ c = Hbc.load("#{tap_path}/Casks/local-caffeine.rb")
+ expect(c).to be_kind_of(Hbc::Cask)
+ expect(c.token).to eq("local-caffeine")
+ end
+
+ it "returns an instance of the Cask from a url" do
+ c = shutup do
+ Hbc.load("file://#{tap_path}/Casks/local-caffeine.rb")
+ end
+ expect(c).to be_kind_of(Hbc::Cask)
+ expect(c.token).to eq("local-caffeine")
+ end
+
+ it "raises an error when failing to download a Cask from a url" do
+ expect {
+ url = "file://#{tap_path}/Casks/notacask.rb"
+ shutup do
+ Hbc.load(url)
+ end
+ }.to raise_error(Hbc::CaskUnavailableError)
+ end
+
+ it "returns an instance of the Cask from a relative file location" do
+ c = Hbc.load(relative_tap_path/"Casks/local-caffeine.rb")
+ expect(c).to be_kind_of(Hbc::Cask)
+ expect(c.token).to eq("local-caffeine")
+ end
+
+ it "uses exact match when loading by token" do
+ expect(Hbc.load("test-opera").token).to eq("test-opera")
+ expect(Hbc.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 {
+ Hbc.load("notacask")
+ }.to raise_error(Hbc::CaskUnavailableError)
+ end
+ end
+
+ describe "all_tokens" do
+ it "returns a token for every Cask" do
+ all_cask_tokens = Hbc.all_tokens
+ expect(all_cask_tokens.count).to be > 20
+ all_cask_tokens.each { |token| expect(token).to be_kind_of(String) }
+ end
+ end
+
+ describe "metadata" do
+ it "proposes a versioned metadata directory name for each instance" do
+ cask_token = "local-caffeine"
+ c = Hbc.load(cask_token)
+ metadata_path = Hbc.caskroom.join(cask_token, ".metadata", c.version)
+ expect(c.metadata_versioned_container_path.to_s).to eq(metadata_path.to_s)
+ end
+ end
+end
diff --git a/Library/Homebrew/test/cask/cli/audit_spec.rb b/Library/Homebrew/test/cask/cli/audit_spec.rb
new file mode 100644
index 000000000..2736e60c1
--- /dev/null
+++ b/Library/Homebrew/test/cask/cli/audit_spec.rb
@@ -0,0 +1,59 @@
+describe Hbc::CLI::Audit, :cask do
+ let(:auditor) { double }
+ let(:cask) { double }
+
+ describe "selection of Casks to audit" do
+ it "audits all Casks if no tokens are given" do
+ allow(Hbc).to receive(:all).and_return([cask, cask])
+
+ expect(auditor).to receive(:audit).twice
+
+ run_audit([], auditor)
+ end
+
+ it "audits specified Casks if tokens are given" do
+ cask_token = "nice-app"
+ expect(Hbc).to receive(:load).with(cask_token).and_return(cask)
+
+ expect(auditor).to receive(:audit).with(cask, audit_download: false, check_token_conflicts: false)
+
+ run_audit([cask_token], auditor)
+ end
+ end
+
+ describe "rules for downloading a Cask" do
+ it "does not download the Cask per default" do
+ allow(Hbc).to receive(:load).and_return(cask)
+ expect(auditor).to receive(:audit).with(cask, audit_download: false, check_token_conflicts: false)
+
+ run_audit(["casktoken"], auditor)
+ end
+
+ it "download a Cask if --download flag is set" do
+ allow(Hbc).to receive(:load).and_return(cask)
+ expect(auditor).to receive(:audit).with(cask, audit_download: true, check_token_conflicts: false)
+
+ run_audit(["casktoken", "--download"], auditor)
+ end
+ end
+
+ describe "rules for checking token conflicts" do
+ it "does not check for token conflicts per default" do
+ allow(Hbc).to receive(:load).and_return(cask)
+ expect(auditor).to receive(:audit).with(cask, audit_download: false, check_token_conflicts: false)
+
+ run_audit(["casktoken"], auditor)
+ end
+
+ it "checks for token conflicts if --token-conflicts flag is set" do
+ allow(Hbc).to receive(:load).and_return(cask)
+ expect(auditor).to receive(:audit).with(cask, audit_download: false, check_token_conflicts: true)
+
+ run_audit(["casktoken", "--token-conflicts"], auditor)
+ end
+ end
+
+ def run_audit(args, auditor)
+ Hbc::CLI::Audit.new(args, auditor).run
+ end
+end
diff --git a/Library/Homebrew/test/cask/cli/cat_spec.rb b/Library/Homebrew/test/cask/cli/cat_spec.rb
new file mode 100644
index 000000000..daf6fb960
--- /dev/null
+++ b/Library/Homebrew/test/cask/cli/cat_spec.rb
@@ -0,0 +1,57 @@
+describe Hbc::CLI::Cat, :cask do
+ describe "given a basic Cask" do
+ let(:expected_output) {
+ <<-EOS.undent
+ cask 'basic-cask' do
+ version '1.2.3'
+ sha256 '8c62a2b791cf5f0da6066a0a4b6e85f62949cd60975da062df44adf887f4370b'
+
+ url 'http://example.com/TestCask.dmg'
+ homepage 'http://example.com/'
+
+ app 'TestCask.app'
+ end
+ EOS
+ }
+
+ it "displays the Cask file content about the specified Cask" do
+ expect {
+ Hbc::CLI::Cat.run("basic-cask")
+ }.to output(expected_output).to_stdout
+ end
+
+ it "throws away additional Cask arguments and uses the first" do
+ expect {
+ Hbc::CLI::Cat.run("basic-cask", "local-caffeine")
+ }.to output(expected_output).to_stdout
+ end
+
+ it "throws away stray options" do
+ expect {
+ Hbc::CLI::Cat.run("--notavalidoption", "basic-cask")
+ }.to output(expected_output).to_stdout
+ end
+ end
+
+ it "raises an exception when the Cask does not exist" do
+ expect {
+ Hbc::CLI::Cat.run("notacask")
+ }.to raise_error(Hbc::CaskUnavailableError)
+ end
+
+ describe "when no Cask is specified" do
+ it "raises an exception" do
+ expect {
+ Hbc::CLI::Cat.run
+ }.to raise_error(Hbc::CaskUnspecifiedError)
+ end
+ end
+
+ describe "when no Cask is specified, but an invalid option" do
+ it "raises an exception" do
+ expect {
+ Hbc::CLI::Cat.run("--notavalidoption")
+ }.to raise_error(Hbc::CaskUnspecifiedError)
+ end
+ end
+end
diff --git a/Library/Homebrew/test/cask/cli/cleanup_spec.rb b/Library/Homebrew/test/cask/cli/cleanup_spec.rb
new file mode 100644
index 000000000..f8578e80d
--- /dev/null
+++ b/Library/Homebrew/test/cask/cli/cleanup_spec.rb
@@ -0,0 +1,93 @@
+describe Hbc::CLI::Cleanup, :cask do
+ let(:cache_location) { Pathname.new(Dir.mktmpdir).realpath }
+ let(:cleanup_outdated) { false }
+
+ subject { described_class.new(cache_location, cleanup_outdated) }
+
+ after do
+ cache_location.rmtree
+ end
+
+ describe "cleanup" do
+ it "removes cached downloads of given casks" do
+ cleaned_up_cached_download = "caffeine"
+
+ cached_downloads = [
+ cache_location.join("#{cleaned_up_cached_download}--latest.zip"),
+ cache_location.join("transmission--2.61.dmg"),
+ ]
+
+ cached_downloads.each(&FileUtils.method(:touch))
+
+ cleanup_size = Hbc::Utils.size_in_bytes(cached_downloads[0])
+
+ expect {
+ subject.cleanup(cleaned_up_cached_download)
+ }.to output(<<-EOS.undent).to_stdout
+ ==> Removing cached downloads for #{cleaned_up_cached_download}
+ #{cached_downloads[0]}
+ ==> This operation has freed approximately #{disk_usage_readable(cleanup_size)} of disk space.
+ EOS
+
+ expect(cached_downloads[0].exist?).to eq(false)
+ expect(cached_downloads[1].exist?).to eq(true)
+ end
+ end
+
+ describe "cleanup!" do
+ it "removes cached downloads" do
+ cached_download = cache_location.join("SomeDownload.dmg")
+ FileUtils.touch(cached_download)
+ cleanup_size = subject.disk_cleanup_size
+
+ expect {
+ subject.cleanup!
+ }.to output(<<-EOS.undent).to_stdout
+ ==> Removing cached downloads
+ #{cached_download}
+ ==> This operation has freed approximately #{disk_usage_readable(cleanup_size)} of disk space.
+ EOS
+
+ expect(cached_download.exist?).to eq(false)
+ end
+
+ # TODO: uncomment when unflaky.
+ # it "does not removed locked files" do
+ # cached_download = cache_location.join("SomeDownload.dmg")
+ # FileUtils.touch(cached_download)
+ # cleanup_size = subject.disk_cleanup_size
+ #
+ # File.new(cached_download).flock(File::LOCK_EX)
+ #
+ # expect(Hbc::Utils).to be_file_locked(cached_download)
+ #
+ # expect {
+ # subject.cleanup!
+ # }.to output(<<-EOS.undent).to_stdout
+ # ==> Removing cached downloads
+ # skipping: #{cached_download} is locked
+ # ==> This operation has freed approximately #{disk_usage_readable(cleanup_size)} of disk space.
+ # EOS
+ #
+ # expect(cached_download.exist?).to eq(true)
+ # end
+
+ context "when cleanup_outdated is specified" do
+ let(:cleanup_outdated) { true }
+
+ it "does not remove cache files newer than 10 days old" do
+ cached_download = cache_location.join("SomeNewDownload.dmg")
+ FileUtils.touch(cached_download)
+
+ expect {
+ subject.cleanup!
+ }.to output(<<-EOS.undent).to_stdout
+ ==> Removing cached downloads older than 10 days old
+ Nothing to do
+ EOS
+
+ expect(cached_download.exist?).to eq(true)
+ end
+ end
+ end
+end
diff --git a/Library/Homebrew/test/cask/cli/create_spec.rb b/Library/Homebrew/test/cask/cli/create_spec.rb
new file mode 100644
index 000000000..21eaeb656
--- /dev/null
+++ b/Library/Homebrew/test/cask/cli/create_spec.rb
@@ -0,0 +1,98 @@
+# monkeypatch for testing
+module Hbc
+ class CLI
+ class Create
+ def self.exec_editor(*command)
+ editor_commands << command
+ end
+
+ def self.reset!
+ @editor_commands = []
+ end
+
+ def self.editor_commands
+ @editor_commands ||= []
+ end
+ end
+ end
+end
+
+describe Hbc::CLI::Create, :cask do
+ before(:each) do
+ Hbc::CLI::Create.reset!
+ end
+
+ after(:each) do
+ %w[new-cask additional-cask another-cask yet-another-cask local-caff].each do |cask|
+ path = Hbc.path(cask)
+ path.delete if path.exist?
+ end
+ end
+
+ it "opens the editor for the specified Cask" do
+ Hbc::CLI::Create.run("new-cask")
+ expect(Hbc::CLI::Create.editor_commands).to eq [
+ [Hbc.path("new-cask")],
+ ]
+ end
+
+ it "drops a template down for the specified Cask" do
+ Hbc::CLI::Create.run("new-cask")
+ template = File.read(Hbc.path("new-cask"))
+ expect(template).to eq <<-EOS.undent
+ cask 'new-cask' do
+ version ''
+ sha256 ''
+
+ url 'https://'
+ name ''
+ homepage ''
+
+ app ''
+ end
+ EOS
+ end
+
+ it "throws away additional Cask arguments and uses the first" do
+ Hbc::CLI::Create.run("additional-cask", "another-cask")
+ expect(Hbc::CLI::Create.editor_commands).to eq [
+ [Hbc.path("additional-cask")],
+ ]
+ end
+
+ it "throws away stray options" do
+ Hbc::CLI::Create.run("--notavalidoption", "yet-another-cask")
+ expect(Hbc::CLI::Create.editor_commands).to eq [
+ [Hbc.path("yet-another-cask")],
+ ]
+ end
+
+ it "raises an exception when the Cask already exists" do
+ expect {
+ Hbc::CLI::Create.run("basic-cask")
+ }.to raise_error(Hbc::CaskAlreadyCreatedError)
+ end
+
+ it "allows creating Casks that are substrings of existing Casks" do
+ Hbc::CLI::Create.run("local-caff")
+ expect(Hbc::CLI::Create.editor_commands).to eq [
+ [Hbc.path("local-caff")],
+ ]
+ end
+
+ describe "when no Cask is specified" do
+ it "raises an exception" do
+ expect {
+ Hbc::CLI::Create.run
+ }.to raise_error(Hbc::CaskUnspecifiedError)
+ end
+ end
+
+ describe "when no Cask is specified, but an invalid option" do
+ it "raises an exception" do
+ expect {
+ Hbc::CLI::Create.run("--notavalidoption")
+ }.to raise_error(Hbc::CaskUnspecifiedError)
+ end
+ end
+end
diff --git a/Library/Homebrew/test/cask/cli/doctor_spec.rb b/Library/Homebrew/test/cask/cli/doctor_spec.rb
new file mode 100644
index 000000000..ff1cf5706
--- /dev/null
+++ b/Library/Homebrew/test/cask/cli/doctor_spec.rb
@@ -0,0 +1,15 @@
+require "hbc/version"
+
+describe Hbc::CLI::Doctor do
+ it "displays some nice info about the environment" do
+ expect {
+ Hbc::CLI::Doctor.run
+ }.to output(/\A==> Homebrew-Cask Version/).to_stdout
+ end
+
+ it "raises an exception when arguments are given" do
+ expect {
+ Hbc::CLI::Doctor.run("argument")
+ }.to raise_error(ArgumentError)
+ end
+end
diff --git a/Library/Homebrew/test/cask/cli/edit_spec.rb b/Library/Homebrew/test/cask/cli/edit_spec.rb
new file mode 100644
index 000000000..61970290b
--- /dev/null
+++ b/Library/Homebrew/test/cask/cli/edit_spec.rb
@@ -0,0 +1,60 @@
+# monkeypatch for testing
+module Hbc
+ class CLI
+ class Edit
+ def self.exec_editor(*command)
+ editor_commands << command
+ end
+
+ def self.reset!
+ @editor_commands = []
+ end
+
+ def self.editor_commands
+ @editor_commands ||= []
+ end
+ end
+ end
+end
+
+describe Hbc::CLI::Edit, :cask do
+ before(:each) do
+ Hbc::CLI::Edit.reset!
+ end
+
+ it "opens the editor for the specified Cask" do
+ Hbc::CLI::Edit.run("local-caffeine")
+ expect(Hbc::CLI::Edit.editor_commands).to eq [
+ [Hbc.path("local-caffeine")],
+ ]
+ end
+
+ it "throws away additional arguments and uses the first" do
+ Hbc::CLI::Edit.run("local-caffeine", "local-transmission")
+ expect(Hbc::CLI::Edit.editor_commands).to eq [
+ [Hbc.path("local-caffeine")],
+ ]
+ end
+
+ it "raises an exception when the Cask doesnt exist" do
+ expect {
+ Hbc::CLI::Edit.run("notacask")
+ }.to raise_error(Hbc::CaskUnavailableError)
+ end
+
+ describe "when no Cask is specified" do
+ it "raises an exception" do
+ expect {
+ Hbc::CLI::Edit.run
+ }.to raise_error(Hbc::CaskUnspecifiedError)
+ end
+ end
+
+ describe "when no Cask is specified, but an invalid option" do
+ it "raises an exception" do
+ expect {
+ Hbc::CLI::Edit.run("--notavalidoption")
+ }.to raise_error(Hbc::CaskUnspecifiedError)
+ end
+ end
+end
diff --git a/Library/Homebrew/test/cask/cli/fetch_spec.rb b/Library/Homebrew/test/cask/cli/fetch_spec.rb
new file mode 100644
index 000000000..1571c2a70
--- /dev/null
+++ b/Library/Homebrew/test/cask/cli/fetch_spec.rb
@@ -0,0 +1,75 @@
+describe Hbc::CLI::Fetch, :cask do
+ let(:local_transmission) {
+ Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/local-transmission.rb")
+ }
+
+ let(:local_caffeine) {
+ Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/local-caffeine.rb")
+ }
+
+ it "allows download the installer of a Cask" do
+ shutup do
+ Hbc::CLI::Fetch.run("local-transmission", "local-caffeine")
+ end
+ expect(Hbc::CurlDownloadStrategy.new(local_transmission).cached_location).to exist
+ expect(Hbc::CurlDownloadStrategy.new(local_caffeine).cached_location).to exist
+ end
+
+ it "prevents double fetch (without nuking existing installation)" do
+ download_stategy = Hbc::CurlDownloadStrategy.new(local_transmission)
+
+ shutup do
+ Hbc::Download.new(local_transmission).perform
+ end
+ old_ctime = File.stat(download_stategy.cached_location).ctime
+
+ shutup do
+ Hbc::CLI::Fetch.run("local-transmission")
+ end
+ new_ctime = File.stat(download_stategy.cached_location).ctime
+
+ expect(old_ctime.to_i).to eq(new_ctime.to_i)
+ end
+
+ it "allows double fetch with --force" do
+ shutup do
+ Hbc::Download.new(local_transmission).perform
+ end
+
+ download_stategy = Hbc::CurlDownloadStrategy.new(local_transmission)
+ old_ctime = File.stat(download_stategy.cached_location).ctime
+ sleep(1)
+
+ shutup do
+ Hbc::CLI::Fetch.run("local-transmission", "--force")
+ end
+ download_stategy = Hbc::CurlDownloadStrategy.new(local_transmission)
+ new_ctime = File.stat(download_stategy.cached_location).ctime
+
+ expect(new_ctime.to_i).to be > old_ctime.to_i
+ end
+
+ it "properly handles Casks that are not present" do
+ expect {
+ shutup do
+ Hbc::CLI::Fetch.run("notacask")
+ end
+ }.to raise_error(Hbc::CaskUnavailableError)
+ end
+
+ describe "when no Cask is specified" do
+ it "raises an exception" do
+ expect {
+ Hbc::CLI::Fetch.run
+ }.to raise_error(Hbc::CaskUnspecifiedError)
+ end
+ end
+
+ describe "when no Cask is specified, but an invalid option" do
+ it "raises an exception" do
+ expect {
+ Hbc::CLI::Fetch.run("--notavalidoption")
+ }.to raise_error(Hbc::CaskUnspecifiedError)
+ end
+ end
+end
diff --git a/Library/Homebrew/test/cask/cli/home_spec.rb b/Library/Homebrew/test/cask/cli/home_spec.rb
new file mode 100644
index 000000000..a5359f24f
--- /dev/null
+++ b/Library/Homebrew/test/cask/cli/home_spec.rb
@@ -0,0 +1,46 @@
+# monkeypatch for testing
+module Hbc
+ class CLI
+ class Home
+ def self.system(*command)
+ system_commands << command
+ end
+
+ def self.reset!
+ @system_commands = []
+ end
+
+ def self.system_commands
+ @system_commands ||= []
+ end
+ end
+ end
+end
+
+describe Hbc::CLI::Home, :cask do
+ before do
+ Hbc::CLI::Home.reset!
+ end
+
+ it "opens the homepage for the specified Cask" do
+ Hbc::CLI::Home.run("local-caffeine")
+ expect(Hbc::CLI::Home.system_commands).to eq [
+ ["/usr/bin/open", "--", "http://example.com/local-caffeine"],
+ ]
+ end
+
+ it "works for multiple Casks" do
+ Hbc::CLI::Home.run("local-caffeine", "local-transmission")
+ expect(Hbc::CLI::Home.system_commands).to eq [
+ ["/usr/bin/open", "--", "http://example.com/local-caffeine"],
+ ["/usr/bin/open", "--", "http://example.com/local-transmission"],
+ ]
+ end
+
+ it "opens the project page when no Cask is specified" do
+ Hbc::CLI::Home.run
+ expect(Hbc::CLI::Home.system_commands).to eq [
+ ["/usr/bin/open", "--", "http://caskroom.io/"],
+ ]
+ end
+end
diff --git a/Library/Homebrew/test/cask/cli/info_spec.rb b/Library/Homebrew/test/cask/cli/info_spec.rb
new file mode 100644
index 000000000..2f70a0b96
--- /dev/null
+++ b/Library/Homebrew/test/cask/cli/info_spec.rb
@@ -0,0 +1,108 @@
+describe Hbc::CLI::Info, :cask do
+ it "displays some nice info about the specified Cask" do
+ expect {
+ Hbc::CLI::Info.run("local-caffeine")
+ }.to output(<<-EOS.undent).to_stdout
+ local-caffeine: 1.2.3
+ http://example.com/local-caffeine
+ Not installed
+ From: https://github.com/caskroom/homebrew-spec/blob/master/Casks/local-caffeine.rb
+ ==> Name
+ None
+ ==> Artifacts
+ Caffeine.app (app)
+ EOS
+ end
+
+ describe "given multiple Casks" do
+ let(:expected_output) {
+ <<-EOS.undent
+ local-caffeine: 1.2.3
+ http://example.com/local-caffeine
+ Not installed
+ From: https://github.com/caskroom/homebrew-spec/blob/master/Casks/local-caffeine.rb
+ ==> Name
+ None
+ ==> Artifacts
+ Caffeine.app (app)
+ local-transmission: 2.61
+ http://example.com/local-transmission
+ Not installed
+ From: https://github.com/caskroom/homebrew-spec/blob/master/Casks/local-transmission.rb
+ ==> Name
+ None
+ ==> Artifacts
+ Transmission.app (app)
+ EOS
+ }
+
+ it "displays the info" do
+ expect {
+ Hbc::CLI::Info.run("local-caffeine", "local-transmission")
+ }.to output(expected_output).to_stdout
+ end
+
+ it "throws away stray options" do
+ expect {
+ Hbc::CLI::Info.run("--notavalidoption", "local-caffeine", "local-transmission")
+ }.to output(expected_output).to_stdout
+ end
+ end
+
+ it "should print caveats if the Cask provided one" do
+ expect {
+ Hbc::CLI::Info.run("with-caveats")
+ }.to output(<<-EOS.undent).to_stdout
+ with-caveats: 1.2.3
+ http://example.com/local-caffeine
+ Not installed
+ From: https://github.com/caskroom/homebrew-spec/blob/master/Casks/with-caveats.rb
+ ==> Name
+ None
+ ==> Artifacts
+ Caffeine.app (app)
+ ==> Caveats
+ Here are some things you might want to know.
+
+ Cask token: with-caveats
+
+ Custom text via puts followed by DSL-generated text:
+ To use with-caveats, you may need to add the /custom/path/bin directory
+ to your PATH environment variable, eg (for bash shell):
+
+ export PATH=/custom/path/bin:"$PATH"
+
+ EOS
+ end
+
+ it 'should not print "Caveats" section divider if the caveats block has no output' do
+ expect {
+ Hbc::CLI::Info.run("with-conditional-caveats")
+ }.to output(<<-EOS.undent).to_stdout
+ with-conditional-caveats: 1.2.3
+ http://example.com/local-caffeine
+ Not installed
+ From: https://github.com/caskroom/homebrew-spec/blob/master/Casks/with-conditional-caveats.rb
+ ==> Name
+ None
+ ==> Artifacts
+ Caffeine.app (app)
+ EOS
+ end
+
+ describe "when no Cask is specified" do
+ it "raises an exception" do
+ expect {
+ Hbc::CLI::Info.run
+ }.to raise_error(Hbc::CaskUnspecifiedError)
+ end
+ end
+
+ describe "when no Cask is specified, but an invalid option" do
+ it "raises an exception" do
+ expect {
+ Hbc::CLI::Info.run("--notavalidoption")
+ }.to raise_error(Hbc::CaskUnspecifiedError)
+ end
+ end
+end
diff --git a/Library/Homebrew/test/cask/cli/install_spec.rb b/Library/Homebrew/test/cask/cli/install_spec.rb
new file mode 100644
index 000000000..5a40017e8
--- /dev/null
+++ b/Library/Homebrew/test/cask/cli/install_spec.rb
@@ -0,0 +1,107 @@
+describe Hbc::CLI::Install, :cask do
+ it "allows staging and activation of multiple Casks at once" do
+ shutup do
+ Hbc::CLI::Install.run("local-transmission", "local-caffeine")
+ end
+
+ expect(Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/local-transmission.rb")).to be_installed
+ expect(Hbc.appdir.join("Transmission.app")).to be_a_directory
+ expect(Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/local-caffeine.rb")).to be_installed
+ expect(Hbc.appdir.join("Caffeine.app")).to be_a_directory
+ end
+
+ it "skips double install (without nuking existing installation)" do
+ shutup do
+ Hbc::CLI::Install.run("local-transmission")
+ end
+ shutup do
+ Hbc::CLI::Install.run("local-transmission")
+ end
+ expect(Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/local-transmission.rb")).to be_installed
+ end
+
+ it "prints a warning message on double install" do
+ shutup do
+ Hbc::CLI::Install.run("local-transmission")
+ end
+
+ expect {
+ Hbc::CLI::Install.run("local-transmission", "")
+ }.to output(/Warning: A Cask for local-transmission is already installed./).to_stderr
+ end
+
+ it "allows double install with --force" do
+ shutup do
+ Hbc::CLI::Install.run("local-transmission")
+ end
+
+ expect {
+ expect {
+ Hbc::CLI::Install.run("local-transmission", "--force")
+ }.to output(/It seems there is already an App at.*overwriting\./).to_stderr
+ }.to output(/local-transmission was successfully installed!/).to_stdout
+ end
+
+ it "skips dependencies with --skip-cask-deps" do
+ shutup do
+ Hbc::CLI::Install.run("with-depends-on-cask-multiple", "--skip-cask-deps")
+ end
+ expect(Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/with-depends-on-cask-multiple.rb")).to be_installed
+ expect(Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/local-caffeine.rb")).not_to be_installed
+ expect(Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/local-transmission.rb")).not_to be_installed
+ end
+
+ it "properly handles Casks that are not present" do
+ expect {
+ shutup do
+ Hbc::CLI::Install.run("notacask")
+ end
+ }.to raise_error(Hbc::CaskError)
+ end
+
+ it "returns a suggestion for a misspelled Cask" do
+ expect {
+ begin
+ Hbc::CLI::Install.run("localcaffeine")
+ rescue Hbc::CaskError
+ nil
+ end
+ }.to output(/No available Cask for localcaffeine\. Did you mean:\nlocal-caffeine/).to_stderr
+ end
+
+ it "returns multiple suggestions for a Cask fragment" do
+ expect {
+ begin
+ Hbc::CLI::Install.run("local-caf")
+ rescue Hbc::CaskError
+ nil
+ end
+ }.to output(/No available Cask for local-caf\. Did you mean one of:\nlocal-caffeine/).to_stderr
+ end
+
+ describe "when no Cask is specified" do
+ with_options = lambda do |options|
+ it "raises an exception" do
+ expect {
+ Hbc::CLI::Install.run(*options)
+ }.to raise_error(Hbc::CaskUnspecifiedError)
+ end
+ end
+
+ describe "without options" do
+ with_options.call([])
+ end
+
+ describe "with --force" do
+ with_options.call(["--force"])
+ end
+
+ describe "with --skip-cask-deps" do
+ with_options.call(["--skip-cask-deps"])
+ end
+
+ describe "with an invalid option" do
+ with_options.call(["--notavalidoption"])
+ end
+ end
+end
diff --git a/Library/Homebrew/test/cask/cli/list_spec.rb b/Library/Homebrew/test/cask/cli/list_spec.rb
new file mode 100644
index 000000000..e367e9588
--- /dev/null
+++ b/Library/Homebrew/test/cask/cli/list_spec.rb
@@ -0,0 +1,82 @@
+describe Hbc::CLI::List, :cask do
+ it "lists the installed Casks in a pretty fashion" do
+ casks = %w[local-caffeine local-transmission].map { |c| Hbc.load(c) }
+
+ casks.each do |c|
+ InstallHelper.install_with_caskfile(c)
+ end
+
+ expect {
+ Hbc::CLI::List.run
+ }.to output(<<-EOS.undent).to_stdout
+ local-caffeine
+ local-transmission
+ EOS
+ end
+
+ describe "lists versions" do
+ let(:casks) { ["local-caffeine", "local-transmission"] }
+ let(:expected_output) {
+ <<-EOS.undent
+ local-caffeine 1.2.3
+ local-transmission 2.61
+ EOS
+ }
+
+ before(:each) do
+ casks.map(&Hbc.method(:load)).each(&InstallHelper.method(:install_with_caskfile))
+ end
+
+ it "of all installed Casks" do
+ expect {
+ Hbc::CLI::List.run("--versions")
+ }.to output(expected_output).to_stdout
+ end
+
+ it "of given Casks" do
+ expect {
+ Hbc::CLI::List.run("--versions", "local-caffeine", "local-transmission")
+ }.to output(expected_output).to_stdout
+ end
+ end
+
+ describe "when Casks have been renamed" do
+ let(:caskroom_path) { Hbc.caskroom.join("ive-been-renamed") }
+ let(:staged_path) { caskroom_path.join("latest") }
+
+ before do
+ staged_path.mkpath
+ end
+
+ it "lists installed Casks without backing ruby files (due to renames or otherwise)" do
+ expect {
+ Hbc::CLI::List.run
+ }.to output(<<-EOS.undent).to_stdout
+ ive-been-renamed (!)
+ EOS
+ end
+ end
+
+ describe "given a set of installed Casks" do
+ let(:caffeine) { Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/local-caffeine.rb") }
+ let(:transmission) { Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/local-transmission.rb") }
+ let(:casks) { [caffeine, transmission] }
+
+ it "lists the installed files for those Casks" do
+ casks.each(&InstallHelper.method(:install_without_artifacts_with_caskfile))
+
+ shutup do
+ Hbc::Artifact::App.new(transmission).install_phase
+ end
+
+ expect {
+ Hbc::CLI::List.run("local-transmission", "local-caffeine")
+ }.to output(<<-EOS.undent).to_stdout
+ ==> Apps
+ #{Hbc.appdir.join("Transmission.app")} (#{Hbc.appdir.join("Transmission.app").abv})
+ ==> Apps
+ Missing App: #{Hbc.appdir.join("Caffeine.app")}
+ EOS
+ end
+ end
+end
diff --git a/Library/Homebrew/test/cask/cli/options_spec.rb b/Library/Homebrew/test/cask/cli/options_spec.rb
new file mode 100644
index 000000000..86933e27e
--- /dev/null
+++ b/Library/Homebrew/test/cask/cli/options_spec.rb
@@ -0,0 +1,138 @@
+describe Hbc::CLI, :cask do
+ it "supports setting the appdir" do
+ Hbc::CLI.process_options %w[help --appdir=/some/path/foo]
+
+ expect(Hbc.appdir).to eq(Pathname.new("/some/path/foo"))
+ end
+
+ it "supports setting the appdir from ENV" do
+ ENV["HOMEBREW_CASK_OPTS"] = "--appdir=/some/path/bar"
+
+ Hbc::CLI.process_options %w[help]
+
+ expect(Hbc.appdir).to eq(Pathname.new("/some/path/bar"))
+ end
+
+ it "supports setting the prefpanedir" do
+ Hbc::CLI.process_options %w[help --prefpanedir=/some/path/foo]
+
+ expect(Hbc.prefpanedir).to eq(Pathname.new("/some/path/foo"))
+ end
+
+ it "supports setting the prefpanedir from ENV" do
+ ENV["HOMEBREW_CASK_OPTS"] = "--prefpanedir=/some/path/bar"
+
+ Hbc::CLI.process_options %w[help]
+
+ expect(Hbc.prefpanedir).to eq(Pathname.new("/some/path/bar"))
+ end
+
+ it "supports setting the qlplugindir" do
+ Hbc::CLI.process_options %w[help --qlplugindir=/some/path/foo]
+
+ expect(Hbc.qlplugindir).to eq(Pathname.new("/some/path/foo"))
+ end
+
+ it "supports setting the qlplugindir from ENV" do
+ ENV["HOMEBREW_CASK_OPTS"] = "--qlplugindir=/some/path/bar"
+
+ Hbc::CLI.process_options %w[help]
+
+ expect(Hbc.qlplugindir).to eq(Pathname.new("/some/path/bar"))
+ end
+
+ it "supports setting the colorpickerdir" do
+ Hbc::CLI.process_options %w[help --colorpickerdir=/some/path/foo]
+
+ expect(Hbc.colorpickerdir).to eq(Pathname.new("/some/path/foo"))
+ end
+
+ it "supports setting the colorpickerdir from ENV" do
+ ENV["HOMEBREW_CASK_OPTS"] = "--colorpickerdir=/some/path/bar"
+
+ Hbc::CLI.process_options %w[help]
+
+ expect(Hbc.colorpickerdir).to eq(Pathname.new("/some/path/bar"))
+ end
+
+ it "supports setting the dictionarydir" do
+ Hbc::CLI.process_options %w[help --dictionarydir=/some/path/foo]
+
+ expect(Hbc.dictionarydir).to eq(Pathname.new("/some/path/foo"))
+ end
+
+ it "supports setting the dictionarydir from ENV" do
+ ENV["HOMEBREW_CASK_OPTS"] = "--dictionarydir=/some/path/bar"
+
+ Hbc::CLI.process_options %w[help]
+
+ expect(Hbc.dictionarydir).to eq(Pathname.new("/some/path/bar"))
+ end
+
+ it "supports setting the fontdir" do
+ Hbc::CLI.process_options %w[help --fontdir=/some/path/foo]
+
+ expect(Hbc.fontdir).to eq(Pathname.new("/some/path/foo"))
+ end
+
+ it "supports setting the fontdir from ENV" do
+ ENV["HOMEBREW_CASK_OPTS"] = "--fontdir=/some/path/bar"
+
+ Hbc::CLI.process_options %w[help]
+
+ expect(Hbc.fontdir).to eq(Pathname.new("/some/path/bar"))
+ end
+
+ it "supports setting the servicedir" do
+ Hbc::CLI.process_options %w[help --servicedir=/some/path/foo]
+
+ expect(Hbc.servicedir).to eq(Pathname.new("/some/path/foo"))
+ end
+
+ it "supports setting the servicedir from ENV" do
+ ENV["HOMEBREW_CASK_OPTS"] = "--servicedir=/some/path/bar"
+
+ Hbc::CLI.process_options %w[help]
+
+ expect(Hbc.servicedir).to eq(Pathname.new("/some/path/bar"))
+ end
+
+ it "allows additional options to be passed through" do
+ rest = Hbc::CLI.process_options %w[edit foo --create --appdir=/some/path/qux]
+
+ expect(Hbc.appdir).to eq(Pathname.new("/some/path/qux"))
+ expect(rest).to eq(%w[edit foo --create])
+ end
+
+ describe "when a mandatory argument is missing" do
+ it "shows a user-friendly error message" do
+ expect {
+ Hbc::CLI.process_options %w[install -f]
+ }.to raise_error(Hbc::CaskError)
+ end
+ end
+
+ describe "given an ambiguous option" do
+ it "shows a user-friendly error message" do
+ expect {
+ Hbc::CLI.process_options %w[edit -c]
+ }.to raise_error(Hbc::CaskError)
+ end
+ end
+
+ describe "--debug" do
+ it "sets the Cask debug method to true" do
+ Hbc::CLI.process_options %w[help --debug]
+ expect(Hbc.debug).to be true
+ Hbc.debug = false
+ end
+ end
+
+ describe "--help" do
+ it "sets the Cask help method to true" do
+ Hbc::CLI.process_options %w[foo --help]
+ expect(Hbc.help).to be true
+ Hbc.help = false
+ end
+ end
+end
diff --git a/Library/Homebrew/test/cask/cli/reinstall_spec.rb b/Library/Homebrew/test/cask/cli/reinstall_spec.rb
new file mode 100644
index 000000000..e573a3470
--- /dev/null
+++ b/Library/Homebrew/test/cask/cli/reinstall_spec.rb
@@ -0,0 +1,22 @@
+describe Hbc::CLI::Reinstall, :cask do
+ it "allows reinstalling a Cask" do
+ shutup do
+ Hbc::CLI::Install.run("local-transmission")
+ end
+ expect(Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/local-transmission.rb")).to be_installed
+
+ shutup do
+ Hbc::CLI::Reinstall.run("local-transmission")
+ end
+ expect(Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/local-transmission.rb")).to be_installed
+ end
+
+ it "allows reinstalling a non installed Cask" do
+ expect(Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/local-transmission.rb")).not_to be_installed
+
+ shutup do
+ Hbc::CLI::Reinstall.run("local-transmission")
+ end
+ expect(Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/local-transmission.rb")).to be_installed
+ end
+end
diff --git a/Library/Homebrew/test/cask/cli/search_spec.rb b/Library/Homebrew/test/cask/cli/search_spec.rb
new file mode 100644
index 000000000..0bcff809a
--- /dev/null
+++ b/Library/Homebrew/test/cask/cli/search_spec.rb
@@ -0,0 +1,59 @@
+describe Hbc::CLI::Search, :cask do
+ it "lists the available Casks that match the search term" do
+ expect {
+ Hbc::CLI::Search.run("local")
+ }.to output(<<-EOS.undent).to_stdout
+ ==> Partial matches
+ local-caffeine
+ local-transmission
+ EOS
+ end
+
+ it "shows that there are no Casks matching a search term that did not result in anything" do
+ expect {
+ Hbc::CLI::Search.run("foo-bar-baz")
+ }.to output("No Cask found for \"foo-bar-baz\".\n").to_stdout
+ end
+
+ it "lists all available Casks with no search term" do
+ expect {
+ Hbc::CLI::Search.run
+ }.to output(/local-caffeine/).to_stdout
+ end
+
+ it "ignores hyphens in search terms" do
+ expect {
+ Hbc::CLI::Search.run("lo-cal-caffeine")
+ }.to output(/local-caffeine/).to_stdout
+ end
+
+ it "ignores hyphens in Cask tokens" do
+ expect {
+ Hbc::CLI::Search.run("localcaffeine")
+ }.to output(/local-caffeine/).to_stdout
+ end
+
+ it "accepts multiple arguments" do
+ expect {
+ Hbc::CLI::Search.run("local caffeine")
+ }.to output(/local-caffeine/).to_stdout
+ end
+
+ it "accepts a regexp argument" do
+ expect {
+ Hbc::CLI::Search.run("/^local-c[a-z]ffeine$/")
+ }.to output("==> Regexp matches\nlocal-caffeine\n").to_stdout
+ end
+
+ it "Returns both exact and partial matches" do
+ expect {
+ Hbc::CLI::Search.run("test-opera")
+ }.to output(/^==> Exact match\ntest-opera\n==> Partial matches\ntest-opera-mail/).to_stdout
+ end
+
+ it "does not search the Tap name" do
+ expect {
+ Hbc::CLI::Search.run("caskroom")
+ }.to output(/^No Cask found for "caskroom"\.\n/).to_stdout
+ end
+end
diff --git a/Library/Homebrew/test/cask/cli/style_spec.rb b/Library/Homebrew/test/cask/cli/style_spec.rb
new file mode 100644
index 000000000..106bfbb44
--- /dev/null
+++ b/Library/Homebrew/test/cask/cli/style_spec.rb
@@ -0,0 +1,235 @@
+require "English"
+require "open3"
+require "rubygems"
+
+describe Hbc::CLI::Style do
+ let(:args) { [] }
+ let(:cli) { described_class.new(args) }
+
+ around do |example|
+ shutup { example.run }
+ end
+
+ describe ".run" do
+ subject { described_class.run(args) }
+
+ before do
+ allow(described_class).to receive(:new).and_return(cli)
+ allow(cli).to receive(:run).and_return(retval)
+ end
+
+ context "when rubocop succeeds" do
+ let(:retval) { true }
+
+ it "exits successfully" do
+ subject
+ end
+ end
+
+ context "when rubocop fails" do
+ let(:retval) { false }
+
+ it "raises an exception" do
+ expect { subject }.to raise_error(Hbc::CaskError)
+ end
+ end
+ end
+
+ describe "#run" do
+ subject { cli.run }
+
+ before do
+ allow(cli).to receive_messages(install_rubocop: nil,
+ system: nil,
+ rubocop_args: nil,
+ cask_paths: nil)
+ allow($CHILD_STATUS).to receive(:success?).and_return(success)
+ end
+
+ context "when rubocop succeeds" do
+ let(:success) { true }
+ it { is_expected.to be_truthy }
+ end
+
+ context "when rubocop fails" do
+ let(:success) { false }
+ it { is_expected.to be_falsey }
+ end
+ end
+
+ describe "#install_rubocop" do
+ subject { cli.install_rubocop }
+
+ context "when installation succeeds" do
+ before do
+ allow(Homebrew).to receive(:install_gem_setup_path!)
+ end
+
+ it "exits successfully" do
+ expect { subject }.not_to raise_error
+ end
+ end
+
+ context "when installation fails" do
+ before do
+ allow(Homebrew).to receive(:install_gem_setup_path!).and_raise(SystemExit)
+ end
+
+ it "raises an error" do
+ expect { subject }.to raise_error(Hbc::CaskError)
+ end
+ end
+
+ context "version" do
+ it "matches `HOMEBREW_RUBOCOP_VERSION`" do
+ stdout, status = Open3.capture2("gem", "dependency", "rubocop-cask", "--version", HOMEBREW_RUBOCOP_CASK_VERSION, "--pipe", "--remote")
+
+ expect(status).to be_a_success
+
+ requirement = Gem::Requirement.new(stdout.scan(/rubocop --version '(.*)'/).flatten.first)
+ version = Gem::Version.new(HOMEBREW_RUBOCOP_VERSION)
+
+ expect(requirement).not_to be_none
+ expect(requirement).to be_satisfied_by(version)
+ end
+ end
+ end
+
+ describe "#cask_paths" do
+ subject { cli.cask_paths }
+
+ before do
+ allow(cli).to receive(:cask_tokens).and_return(tokens)
+ end
+
+ context "when no cask tokens are given" do
+ let(:tokens) { [] }
+
+ before do
+ allow(Hbc).to receive(:all_tapped_cask_dirs).and_return(%w[Casks MoreCasks])
+ end
+
+ it { is_expected.to eq(%w[Casks MoreCasks]) }
+ end
+
+ context "when at least one cask token is a path that exists" do
+ let(:tokens) { ["adium", "Casks/dropbox.rb"] }
+ before do
+ allow(File).to receive(:exist?).and_return(false, true)
+ end
+
+ it "treats all tokens as paths" do
+ expect(subject).to eq(tokens)
+ end
+ end
+
+ context "when no cask tokens are paths that exist" do
+ let(:tokens) { %w[adium dropbox] }
+ before do
+ allow(File).to receive(:exist?).and_return(false)
+ end
+
+ it "tries to find paths for all tokens" do
+ expect(Hbc).to receive(:path).twice
+ subject
+ end
+ end
+ end
+
+ describe "#cask_tokens" do
+ subject { cli.cask_tokens }
+
+ context "when no args are given" do
+ let(:args) { [] }
+ it { is_expected.to be_empty }
+ end
+
+ context "when only flags are given" do
+ let(:args) { ["--fix"] }
+ it { is_expected.to be_empty }
+ end
+
+ context "when only empty args are given" do
+ let(:args) { ["", ""] }
+ it { is_expected.to be_empty }
+ end
+
+ context "when a cask token is given" do
+ let(:args) { ["adium"] }
+ it { is_expected.to eq(["adium"]) }
+ end
+
+ context "when multiple cask tokens are given" do
+ let(:args) { %w[adium dropbox] }
+ it { is_expected.to eq(%w[adium dropbox]) }
+ end
+
+ context "when cask tokens are given with flags" do
+ let(:args) { ["adium", "dropbox", "--fix"] }
+ it { is_expected.to eq(%w[adium dropbox]) }
+ end
+ end
+
+ describe "#rubocop_args" do
+ subject { cli.rubocop_args }
+
+ before do
+ allow(cli).to receive(:fix?).and_return(fix)
+ end
+
+ context "when fix? is true" do
+ let(:fix) { true }
+ it { is_expected.to include("--auto-correct") }
+ end
+
+ context "when fix? is false" do
+ let(:fix) { false }
+ it { is_expected.not_to include("--auto-correct") }
+ end
+ end
+
+ describe "#default_args" do
+ subject { cli.default_args }
+
+ it { is_expected.to include("--require", "rubocop-cask", "--format", "simple", "--force-exclusion") }
+ end
+
+ describe "#autocorrect_args" do
+ subject { cli.autocorrect_args }
+ let(:default_args) { ["--format", "simple"] }
+
+ it "should add --auto-correct to default args" do
+ allow(cli).to receive(:default_args).and_return(default_args)
+ expect(subject).to include("--auto-correct", *default_args)
+ end
+ end
+
+ describe "#fix?" do
+ subject { cli.fix? }
+
+ context "when --fix is passed as an argument" do
+ let(:args) { ["adium", "--fix"] }
+ it { is_expected.to be_truthy }
+ end
+
+ context "when --correct is passed as an argument" do
+ let(:args) { ["adium", "--correct"] }
+ it { is_expected.to be_truthy }
+ end
+
+ context "when --auto-correct is passed as an argument" do
+ let(:args) { ["adium", "--auto-correct"] }
+ it { is_expected.to be_truthy }
+ end
+
+ context "when --auto-correct is misspelled as --autocorrect" do
+ let(:args) { ["adium", "--autocorrect"] }
+ it { is_expected.to be_truthy }
+ end
+
+ context "when no flag equivalent to --fix is passed as an argument" do
+ let(:args) { ["adium"] }
+ it { is_expected.to be_falsey }
+ end
+ end
+end
diff --git a/Library/Homebrew/test/cask/cli/uninstall_spec.rb b/Library/Homebrew/test/cask/cli/uninstall_spec.rb
new file mode 100644
index 000000000..cbfb3e237
--- /dev/null
+++ b/Library/Homebrew/test/cask/cli/uninstall_spec.rb
@@ -0,0 +1,189 @@
+describe Hbc::CLI::Uninstall, :cask do
+ it "shows an error when a bad Cask is provided" do
+ expect {
+ Hbc::CLI::Uninstall.run("notacask")
+ }.to raise_error(Hbc::CaskUnavailableError)
+ end
+
+ it "shows an error when a Cask is provided that's not installed" do
+ expect {
+ Hbc::CLI::Uninstall.run("local-caffeine")
+ }.to raise_error(Hbc::CaskNotInstalledError)
+ end
+
+ it "tries anyway on a non-present Cask when --force is given" do
+ expect {
+ Hbc::CLI::Uninstall.run("local-caffeine", "--force")
+ }.not_to raise_error
+ end
+
+ it "can uninstall and unlink multiple Casks at once" do
+ caffeine = Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/local-caffeine.rb")
+ transmission = Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/local-transmission.rb")
+
+ shutup do
+ Hbc::Installer.new(caffeine).install
+ Hbc::Installer.new(transmission).install
+ end
+
+ expect(caffeine).to be_installed
+ expect(transmission).to be_installed
+
+ shutup do
+ Hbc::CLI::Uninstall.run("local-caffeine", "local-transmission")
+ end
+
+ expect(caffeine).not_to be_installed
+ expect(Hbc.appdir.join("Transmission.app")).not_to exist
+ expect(transmission).not_to be_installed
+ expect(Hbc.appdir.join("Caffeine.app")).not_to exist
+ end
+
+ it "calls `uninstall` before removing artifacts" do
+ cask = Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/with-uninstall-script-app.rb")
+
+ shutup do
+ Hbc::Installer.new(cask).install
+ end
+
+ expect(cask).to be_installed
+
+ expect {
+ shutup do
+ Hbc::CLI::Uninstall.run("with-uninstall-script-app")
+ end
+ }.not_to raise_error
+
+ expect(cask).not_to be_installed
+ expect(Hbc.appdir.join("MyFancyApp.app")).not_to exist
+ end
+
+ it "can uninstall Casks when the uninstall script is missing, but only when using `--force`" do
+ cask = Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/with-uninstall-script-app.rb")
+
+ shutup do
+ Hbc::Installer.new(cask).install
+ end
+
+ expect(cask).to be_installed
+
+ Hbc.appdir.join("MyFancyApp.app").rmtree
+
+ expect {
+ shutup do
+ Hbc::CLI::Uninstall.run("with-uninstall-script-app")
+ end
+ }.to raise_error(Hbc::CaskError, /does not exist/)
+
+ expect(cask).to be_installed
+
+ expect {
+ shutup do
+ Hbc::CLI::Uninstall.run("with-uninstall-script-app", "--force")
+ end
+ }.not_to raise_error
+
+ expect(cask).not_to be_installed
+ end
+
+ describe "when multiple versions of a cask are installed" do
+ let(:token) { "versioned-cask" }
+ let(:first_installed_version) { "1.2.3" }
+ let(:last_installed_version) { "4.5.6" }
+ let(:timestamped_versions) {
+ [
+ [first_installed_version, "123000"],
+ [last_installed_version, "456000"],
+ ]
+ }
+ let(:caskroom_path) { Hbc.caskroom.join(token).tap(&:mkpath) }
+
+ before(:each) do
+ timestamped_versions.each do |timestamped_version|
+ caskroom_path.join(".metadata", *timestamped_version, "Casks").tap(&:mkpath)
+ .join("#{token}.rb").open("w") do |caskfile|
+ caskfile.puts <<-EOS.undent
+ cask '#{token}' do
+ version '#{timestamped_version[0]}'
+ end
+ EOS
+ end
+ caskroom_path.join(timestamped_version[0]).mkpath
+ end
+ end
+
+ it "uninstalls one version at a time" do
+ shutup do
+ Hbc::CLI::Uninstall.run("versioned-cask")
+ end
+
+ expect(caskroom_path.join(first_installed_version)).to exist
+ expect(caskroom_path.join(last_installed_version)).not_to exist
+ expect(caskroom_path).to exist
+
+ shutup do
+ Hbc::CLI::Uninstall.run("versioned-cask")
+ end
+
+ expect(caskroom_path.join(first_installed_version)).not_to exist
+ expect(caskroom_path).not_to exist
+ end
+
+ it "displays a message when versions remain installed" do
+ expect {
+ expect {
+ Hbc::CLI::Uninstall.run("versioned-cask")
+ }.not_to output.to_stderr
+ }.to output(/#{token} #{first_installed_version} is still installed./).to_stdout
+ end
+ end
+
+ describe "when Casks in Taps have been renamed or removed" do
+ let(:app) { Hbc.appdir.join("ive-been-renamed.app") }
+ let(:caskroom_path) { Hbc.caskroom.join("ive-been-renamed").tap(&:mkpath) }
+ let(:saved_caskfile) { caskroom_path.join(".metadata", "latest", "timestamp", "Casks").join("ive-been-renamed.rb") }
+
+ before do
+ app.tap(&:mkpath)
+ .join("Contents").tap(&:mkpath)
+ .join("Info.plist").tap(&FileUtils.method(:touch))
+
+ caskroom_path.mkpath
+
+ saved_caskfile.dirname.mkpath
+
+ IO.write saved_caskfile, <<-EOS.undent
+ cask 'ive-been-renamed' do
+ version :latest
+
+ app 'ive-been-renamed.app'
+ end
+ EOS
+ end
+
+ it "can still uninstall those Casks" do
+ shutup do
+ Hbc::CLI::Uninstall.run("ive-been-renamed")
+ end
+
+ expect(app).not_to exist
+ expect(caskroom_path).not_to exist
+ end
+ end
+
+ describe "when no Cask is specified" do
+ it "raises an exception" do
+ expect {
+ Hbc::CLI::Uninstall.run
+ }.to raise_error(Hbc::CaskUnspecifiedError)
+ end
+ end
+
+ describe "when no Cask is specified, but an invalid option" do
+ it "raises an exception" do
+ expect {
+ Hbc::CLI::Uninstall.run("--notavalidoption")
+ }.to raise_error(Hbc::CaskUnspecifiedError)
+ end
+ end
+end
diff --git a/Library/Homebrew/test/cask/cli/version_spec.rb b/Library/Homebrew/test/cask/cli/version_spec.rb
new file mode 100644
index 000000000..2091496fc
--- /dev/null
+++ b/Library/Homebrew/test/cask/cli/version_spec.rb
@@ -0,0 +1,9 @@
+describe "brew cask --version", :cask do
+ it "respects the --version argument" do
+ expect {
+ expect {
+ Hbc::CLI::NullCommand.new("--version").run
+ }.not_to output.to_stderr
+ }.to output(Hbc.full_version).to_stdout
+ end
+end
diff --git a/Library/Homebrew/test/cask/cli/zap_spec.rb b/Library/Homebrew/test/cask/cli/zap_spec.rb
new file mode 100644
index 000000000..0f3d024b5
--- /dev/null
+++ b/Library/Homebrew/test/cask/cli/zap_spec.rb
@@ -0,0 +1,73 @@
+describe Hbc::CLI::Zap, :cask do
+ it "shows an error when a bad Cask is provided" do
+ expect {
+ Hbc::CLI::Zap.run("notacask")
+ }.to raise_error(Hbc::CaskUnavailableError)
+ end
+
+ it "can zap and unlink multiple Casks at once" do
+ caffeine = Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/local-caffeine.rb")
+ transmission = Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/local-transmission.rb")
+
+ shutup do
+ Hbc::Installer.new(caffeine).install
+ Hbc::Installer.new(transmission).install
+ end
+
+ expect(caffeine).to be_installed
+ expect(transmission).to be_installed
+
+ shutup do
+ Hbc::CLI::Zap.run("--notavalidoption",
+ "local-caffeine", "local-transmission")
+ end
+
+ expect(caffeine).not_to be_installed
+ expect(Hbc.appdir.join("Caffeine.app")).not_to be_a_symlink
+ expect(transmission).not_to be_installed
+ expect(Hbc.appdir.join("Transmission.app")).not_to be_a_symlink
+ end
+
+ # TODO: Explicit test that both zap and uninstall directives get dispatched.
+ # The above tests that implicitly.
+ #
+ # it "dispatches both uninstall and zap stanzas" do
+ # with_zap = Hbc.load('with-zap')
+ #
+ # shutup do
+ # Hbc::Installer.new(with_zap).install
+ # end
+ #
+ # with_zap.must_be :installed?
+ #
+ # Hbc::FakeSystemCommand.stubs_command(['/usr/bin/sudo', '-E', '--', '/usr/bin/osascript', '-e', 'tell application "System Events" to count processes whose bundle identifier is "my.fancy.package.app"'], '1')
+ # Hbc::FakeSystemCommand.stubs_command(['/usr/bin/sudo', '-E', '--', '/usr/bin/osascript', '-e', 'tell application id "my.fancy.package.app" to quit'])
+ # Hbc::FakeSystemCommand.stubs_command(['/usr/bin/sudo', '-E', '--', '/usr/bin/osascript', '-e', 'tell application "System Events" to count processes whose bundle identifier is "my.fancy.package.app.from.uninstall"'], '1')
+ # Hbc::FakeSystemCommand.stubs_command(['/usr/bin/sudo', '-E', '--', '/usr/bin/osascript', '-e', 'tell application id "my.fancy.package.app.from.uninstall" to quit'])
+ #
+ # Hbc::FakeSystemCommand.expects_command(['/usr/bin/sudo', '-E', '--', with_zap.staged_path.join('MyFancyPkg','FancyUninstaller.tool'), '--please'])
+ # Hbc::FakeSystemCommand.expects_command(['/usr/bin/sudo', '-E', '--', '/bin/rm', '-rf', '--',
+ # Pathname.new('~/Library/Preferences/my.fancy.app.plist').expand_path])
+ #
+ # shutup do
+ # Hbc::CLI::Zap.run('with-zap')
+ # end
+ # with_zap.wont_be :installed?
+ # end
+
+ describe "when no Cask is specified" do
+ it "raises an exception" do
+ expect {
+ Hbc::CLI::Zap.run
+ }.to raise_error(Hbc::CaskUnspecifiedError)
+ end
+ end
+
+ describe "when no Cask is specified, but an invalid option" do
+ it "raises an exception" do
+ expect {
+ Hbc::CLI::Zap.run("--notavalidoption")
+ }.to raise_error(Hbc::CaskUnspecifiedError)
+ end
+ end
+end
diff --git a/Library/Homebrew/test/cask/cli_spec.rb b/Library/Homebrew/test/cask/cli_spec.rb
new file mode 100644
index 000000000..1ad6790a3
--- /dev/null
+++ b/Library/Homebrew/test/cask/cli_spec.rb
@@ -0,0 +1,61 @@
+describe Hbc::CLI, :cask do
+ it "lists the taps for Casks that show up in two taps" do
+ listing = Hbc::CLI.nice_listing(%w[
+ caskroom/cask/adium
+ caskroom/cask/google-chrome
+ passcod/homebrew-cask/adium
+ ])
+
+ expect(listing).to eq(%w[
+ caskroom/cask/adium
+ google-chrome
+ passcod/cask/adium
+ ])
+ end
+
+ context ".process" do
+ let(:noop_command) { double("CLI::Noop") }
+
+ before do
+ allow(Hbc).to receive(:init)
+ allow(described_class).to receive(:lookup_command).with("noop").and_return(noop_command)
+ allow(noop_command).to receive(:run)
+ end
+
+ around do |example|
+ shutup { example.run }
+ end
+
+ it "passes `--version` along to the subcommand" do
+ expect(described_class).to receive(:run_command).with(noop_command, "--version")
+ described_class.process(%w[noop --version])
+ end
+
+ it "prints help output when subcommand receives `--help` flag" do
+ expect(described_class).to receive(:run_command).with("help")
+ described_class.process(%w[noop --help])
+ expect(Hbc.help).to eq(true)
+ Hbc.help = false
+ end
+
+ it "respects the env variable when choosing what appdir to create" do
+ allow(ENV).to receive(:[])
+ allow(ENV).to receive(:[]).with("HOMEBREW_CASK_OPTS").and_return("--appdir=/custom/appdir")
+ expect(Hbc).to receive(:appdir=).with(Pathname.new("/custom/appdir"))
+ described_class.process("noop")
+ end
+
+ it "respects the env variable when choosing a non-default Caskroom location" do
+ allow(ENV).to receive(:[])
+ allow(ENV).to receive(:[]).with("HOMEBREW_CASK_OPTS").and_return("--caskroom=/custom/caskdir")
+ expect(Hbc).to receive(:caskroom=).with(Pathname.new("/custom/caskdir"))
+ described_class.process("noop")
+ end
+
+ it "exits with a status of 1 when something goes wrong" do
+ allow(described_class).to receive(:lookup_command).and_raise(Hbc::CaskError)
+ expect(described_class).to receive(:exit).with(1)
+ described_class.process("noop")
+ end
+ end
+end
diff --git a/Library/Homebrew/test/cask/container/dmg_spec.rb b/Library/Homebrew/test/cask/container/dmg_spec.rb
new file mode 100644
index 000000000..a94362aba
--- /dev/null
+++ b/Library/Homebrew/test/cask/container/dmg_spec.rb
@@ -0,0 +1,20 @@
+describe Hbc::Container::Dmg, :cask do
+ describe "#mount!" do
+ it "does not store nil mounts for dmgs with extra data" do
+ transmission = Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/local-transmission.rb")
+
+ dmg = Hbc::Container::Dmg.new(
+ transmission,
+ Pathname(transmission.url.path),
+ Hbc::SystemCommand,
+ )
+
+ begin
+ dmg.mount!
+ expect(dmg.mounts).not_to include nil
+ ensure
+ dmg.eject!
+ end
+ end
+ end
+end
diff --git a/Library/Homebrew/test/cask/container/naked_spec.rb b/Library/Homebrew/test/cask/container/naked_spec.rb
new file mode 100644
index 000000000..eb30ef81a
--- /dev/null
+++ b/Library/Homebrew/test/cask/container/naked_spec.rb
@@ -0,0 +1,23 @@
+describe Hbc::Container::Naked, :cask do
+ it "saves files with spaces in them from uris with encoded spaces" do
+ cask = Hbc::Cask.new("spacey") do
+ url "http://example.com/kevin%20spacey.pkg"
+ version "1.2"
+ end
+
+ path = "/tmp/downloads/kevin-spacey-1.2.pkg"
+ expected_destination = cask.staged_path.join("kevin spacey.pkg")
+ expected_command = ["/usr/bin/ditto", "--", path, expected_destination]
+ Hbc::FakeSystemCommand.stubs_command(expected_command)
+
+ container = Hbc::Container::Naked.new(cask, path, Hbc::FakeSystemCommand)
+
+ expect {
+ shutup do
+ container.extract
+ end
+ }.not_to raise_error
+
+ expect(Hbc::FakeSystemCommand.system_calls[expected_command]).to eq(1)
+ end
+end
diff --git a/Library/Homebrew/test/cask/depends_on_spec.rb b/Library/Homebrew/test/cask/depends_on_spec.rb
new file mode 100644
index 000000000..81fda2329
--- /dev/null
+++ b/Library/Homebrew/test/cask/depends_on_spec.rb
@@ -0,0 +1,90 @@
+# TODO: this test should be named after the corresponding class, once
+# that class is abstracted from installer.rb
+describe "Satisfy Dependencies and Requirements", :cask do
+ subject {
+ lambda do
+ shutup do
+ Hbc::Installer.new(cask).install
+ end
+ end
+ }
+
+ describe "depends_on cask" do
+ context "when depends_on cask is cyclic" do
+ let(:cask) { Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/with-depends-on-cask-cyclic.rb") }
+ it { is_expected.to raise_error(Hbc::CaskCyclicCaskDependencyError) }
+ end
+
+ context do
+ let(:cask) { Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/with-depends-on-cask.rb") }
+ let(:dependency) { Hbc.load(cask.depends_on.cask.first) }
+
+ it "installs the dependency of a Cask and the Cask itself" do
+ expect(subject).not_to raise_error
+ expect(cask).to be_installed
+ expect(dependency).to be_installed
+ end
+ end
+ end
+
+ describe "depends_on macos" do
+ context "given an array" do
+ let(:cask) { Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/with-depends-on-macos-array.rb") }
+ it { is_expected.not_to raise_error }
+ end
+
+ context "given a comparisson" do
+ let(:cask) { Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/with-depends-on-macos-comparison.rb") }
+ it { is_expected.not_to raise_error }
+ end
+
+ context "given a string" do
+ let(:cask) { Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/with-depends-on-macos-string.rb") }
+ it { is_expected.not_to raise_error }
+ end
+
+ context "given a symbol" do
+ let(:cask) { Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/with-depends-on-macos-symbol.rb") }
+ it { is_expected.not_to raise_error }
+ end
+
+ context "when not satisfied" do
+ let(:cask) { Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/with-depends-on-macos-failure.rb") }
+ it { is_expected.to raise_error(Hbc::CaskError) }
+ end
+ end
+
+ describe "depends_on arch" do
+ context "when satisfied" do
+ let(:cask) { Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/with-depends-on-arch.rb") }
+ it { is_expected.not_to raise_error }
+ end
+ end
+
+ describe "depends_on x11" do
+ before(:each) do
+ allow(MacOS::X11).to receive(:installed?).and_return(x11_installed)
+ end
+
+ context "when satisfied" do
+ let(:cask) { Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/with-depends-on-x11.rb") }
+ let(:x11_installed) { true }
+
+ it { is_expected.not_to raise_error }
+ end
+
+ context "when not satisfied" do
+ let(:cask) { Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/with-depends-on-x11.rb") }
+ let(:x11_installed) { false }
+
+ it { is_expected.to raise_error(Hbc::CaskX11DependencyError) }
+ end
+
+ context "when depends_on x11: false" do
+ let(:cask) { Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/with-depends-on-x11-false.rb") }
+ let(:x11_installed) { false }
+
+ it { is_expected.not_to raise_error }
+ end
+ end
+end
diff --git a/Library/Homebrew/test/cask/download_strategy_spec.rb b/Library/Homebrew/test/cask/download_strategy_spec.rb
new file mode 100644
index 000000000..ca082c581
--- /dev/null
+++ b/Library/Homebrew/test/cask/download_strategy_spec.rb
@@ -0,0 +1,306 @@
+describe "download strategies", :cask do
+ let(:url) { "http://example.com/cask.dmg" }
+ let(:url_options) { Hash.new }
+ let(:cask) {
+ instance_double(Hbc::Cask, token: "some-cask",
+ url: Hbc::URL.new(url, url_options),
+ version: "1.2.3.4")
+ }
+
+ describe Hbc::CurlDownloadStrategy do
+ let(:downloader) { Hbc::CurlDownloadStrategy.new(cask) }
+
+ before do
+ allow(downloader.temporary_path).to receive(:rename)
+ end
+
+ it "properly assigns a name and uri based on the Cask" do
+ expect(downloader.name).to eq("some-cask")
+ expect(downloader.url).to eq("http://example.com/cask.dmg")
+ expect(downloader.version.to_s).to eq("1.2.3.4")
+ end
+
+ it "calls curl with default arguments for a simple Cask" do
+ allow(downloader).to receive(:curl)
+
+ shutup do
+ downloader.fetch
+ end
+
+ expect(downloader).to have_received(:curl).with(
+ cask.url.to_s,
+ "-C", 0,
+ "-o", kind_of(Pathname)
+ )
+ end
+
+ context "with an explicit user agent" do
+ let(:url_options) { { user_agent: "Mozilla/25.0.1" } }
+
+ it "adds the appropriate curl args" do
+ curl_args = []
+ allow(downloader).to receive(:curl) { |*args| curl_args = args }
+
+ shutup do
+ downloader.fetch
+ end
+
+ expect(curl_args.each_cons(2)).to include(["-A", "Mozilla/25.0.1"])
+ end
+ end
+
+ context "with a generalized fake user agent" do
+ let(:url_options) { { user_agent: :fake } }
+
+ it "adds the appropriate curl args" do
+ curl_args = []
+ allow(downloader).to receive(:curl) { |*args| curl_args = args }
+
+ shutup do
+ downloader.fetch
+ end
+
+ expect(curl_args.each_cons(2)).to include(["-A", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10) http://caskroom.io"])
+ end
+ end
+
+ context "with cookies set" do
+ let(:url_options) {
+ {
+ cookies: {
+ coo: "kie",
+ mon: "ster",
+ },
+ }
+ }
+
+ it "adds curl args for cookies" do
+ curl_args = []
+ allow(downloader).to receive(:curl) { |*args| curl_args = args }
+
+ shutup do
+ downloader.fetch
+ end
+
+ expect(curl_args.each_cons(2)).to include(["-b", "coo=kie;mon=ster"])
+ end
+ end
+
+ context "with referer set" do
+ let(:url_options) { { referer: "http://somehost/also" } }
+
+ it "adds curl args for referer" do
+ curl_args = []
+ allow(downloader).to receive(:curl) { |*args| curl_args = args }
+
+ shutup do
+ downloader.fetch
+ end
+
+ expect(curl_args.each_cons(2)).to include(["-e", "http://somehost/also"])
+ end
+ end
+ end
+
+ describe Hbc::CurlPostDownloadStrategy do
+ let(:downloader) { Hbc::CurlPostDownloadStrategy.new(cask) }
+
+ before do
+ allow(downloader.temporary_path).to receive(:rename)
+ end
+
+ context "with :using and :data specified" do
+ let(:url_options) {
+ {
+ using: :post,
+ data: {
+ form: "data",
+ is: "good",
+ },
+ }
+ }
+
+ it "adds curl args for post arguments" do
+ curl_args = []
+ allow(downloader).to receive(:curl) { |*args| curl_args = args }
+
+ shutup do
+ downloader.fetch
+ end
+
+ expect(curl_args.each_cons(2)).to include(["-d", "form=data"])
+ expect(curl_args.each_cons(2)).to include(["-d", "is=good"])
+ end
+ end
+
+ context "with :using but no :data" do
+ let(:url_options) { { using: :post } }
+
+ it "adds curl args for a POST request" do
+ curl_args = []
+ allow(downloader).to receive(:curl) { |*args| curl_args = args }
+
+ shutup do
+ downloader.fetch
+ end
+
+ expect(curl_args.each_cons(2)).to include(["-X", "POST"])
+ end
+ end
+ end
+
+ describe Hbc::SubversionDownloadStrategy do
+ let(:url_options) { { using: :svn } }
+ let(:fake_system_command) { class_double(Hbc::SystemCommand) }
+ let(:downloader) { Hbc::SubversionDownloadStrategy.new(cask, fake_system_command) }
+ before do
+ allow(fake_system_command).to receive(:run!)
+ end
+
+ it "returns a tarball path on fetch" do
+ allow(downloader).to receive(:compress)
+ allow(downloader).to receive(:fetch_repo)
+
+ retval = shutup { downloader.fetch }
+
+ expect(retval).to equal(downloader.tarball_path)
+ end
+
+ it "calls fetch_repo with default arguments for a simple Cask" do
+ allow(downloader).to receive(:compress)
+ allow(downloader).to receive(:fetch_repo)
+
+ shutup do
+ downloader.fetch
+ end
+
+ expect(downloader).to have_received(:fetch_repo).with(
+ downloader.cached_location,
+ cask.url.to_s,
+ )
+ end
+
+ it "calls svn with default arguments for a simple Cask" do
+ allow(downloader).to receive(:compress)
+
+ shutup do
+ downloader.fetch
+ end
+
+ expect(fake_system_command).to have_received(:run!).with(
+ "/usr/bin/svn",
+ hash_including(args: [
+ "checkout",
+ "--force",
+ "--config-option",
+ "config:miscellany:use-commit-times=yes",
+ cask.url.to_s,
+ downloader.cached_location,
+ ]),
+ )
+ end
+
+ context "with trust_cert set on the URL" do
+ let(:url_options) {
+ {
+ using: :svn,
+ trust_cert: true,
+ }
+ }
+
+ it "adds svn arguments for :trust_cert" do
+ allow(downloader).to receive(:compress)
+
+ shutup do
+ downloader.fetch
+ end
+
+ expect(fake_system_command).to have_received(:run!).with(
+ "/usr/bin/svn",
+ hash_including(args: [
+ "checkout",
+ "--force",
+ "--config-option",
+ "config:miscellany:use-commit-times=yes",
+ "--trust-server-cert",
+ "--non-interactive",
+ cask.url.to_s,
+ downloader.cached_location,
+ ]),
+ )
+ end
+ end
+
+ context "with :revision set on url" do
+ let(:url_options) {
+ {
+ using: :svn,
+ revision: "10",
+ }
+ }
+
+ it "adds svn arguments for :revision" do
+ allow(downloader).to receive(:compress)
+
+ shutup do
+ downloader.fetch
+ end
+
+ expect(fake_system_command).to have_received(:run!).with(
+ "/usr/bin/svn",
+ hash_including(args: [
+ "checkout",
+ "--force",
+ "--config-option",
+ "config:miscellany:use-commit-times=yes",
+ cask.url.to_s,
+ downloader.cached_location,
+ "-r",
+ "10",
+ ]),
+ )
+ end
+ end
+
+ it "runs tar to serialize svn downloads" do
+ # sneaky stub to remake the directory, since homebrew code removes it
+ # before tar is called
+ allow(downloader).to receive(:fetch_repo) {
+ downloader.cached_location.mkdir
+ }
+
+ shutup do
+ downloader.fetch
+ end
+
+ expect(fake_system_command).to have_received(:run!).with(
+ "/usr/bin/tar",
+ hash_including(args: [
+ '-s/^\\.//',
+ "--exclude",
+ ".svn",
+ "-cf",
+ downloader.tarball_path,
+ "--",
+ ".",
+ ]),
+ )
+ end
+ end
+
+ # does not work yet, because (for unknown reasons), the tar command
+ # returns an error code when running under the test suite
+ # it 'creates a tarball matching the expected checksum' do
+ # cask = Hbc.load('svn-download-check-cask')
+ # downloader = Hbc::SubversionDownloadStrategy.new(cask)
+ # # special mocking required for tar to have something to work with
+ # def downloader.fetch_repo(target, url, revision = nil, ignore_externals=false)
+ # target.mkpath
+ # FileUtils.touch(target.join('empty_file.txt'))
+ # File.utime(1000,1000,target.join('empty_file.txt'))
+ # end
+ # expect(shutup { downloader.fetch }).to equal(downloader.tarball_path)
+ # d = Hbc::Download.new(cask)
+ # d.send(:_check_sums, downloader.tarball_path, cask.sums)
+ # end
+end
diff --git a/Library/Homebrew/test/cask/dsl/caveats_spec.rb b/Library/Homebrew/test/cask/dsl/caveats_spec.rb
new file mode 100644
index 000000000..aa662e4d0
--- /dev/null
+++ b/Library/Homebrew/test/cask/dsl/caveats_spec.rb
@@ -0,0 +1,10 @@
+require "test/support/helper/spec/shared_examples/hbc_dsl_base"
+
+describe Hbc::DSL::Caveats, :cask do
+ let(:cask) { Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/basic-cask.rb") }
+ let(:dsl) { Hbc::DSL::Caveats.new(cask) }
+
+ it_behaves_like Hbc::DSL::Base
+
+ # TODO: add tests for Caveats DSL methods
+end
diff --git a/Library/Homebrew/test/cask/dsl/postflight_spec.rb b/Library/Homebrew/test/cask/dsl/postflight_spec.rb
new file mode 100644
index 000000000..d2b080ca3
--- /dev/null
+++ b/Library/Homebrew/test/cask/dsl/postflight_spec.rb
@@ -0,0 +1,13 @@
+require "test/support/helper/spec/shared_examples/hbc_dsl_base"
+require "test/support/helper/spec/shared_examples/hbc_staged"
+
+describe Hbc::DSL::Postflight, :cask do
+ let(:cask) { Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/basic-cask.rb") }
+ let(:dsl) { Hbc::DSL::Postflight.new(cask, Hbc::FakeSystemCommand) }
+
+ it_behaves_like Hbc::DSL::Base
+
+ it_behaves_like Hbc::Staged do
+ let(:staged) { dsl }
+ end
+end
diff --git a/Library/Homebrew/test/cask/dsl/preflight_spec.rb b/Library/Homebrew/test/cask/dsl/preflight_spec.rb
new file mode 100644
index 000000000..b93be95ff
--- /dev/null
+++ b/Library/Homebrew/test/cask/dsl/preflight_spec.rb
@@ -0,0 +1,13 @@
+require "test/support/helper/spec/shared_examples/hbc_dsl_base"
+require "test/support/helper/spec/shared_examples/hbc_staged"
+
+describe Hbc::DSL::Preflight, :cask do
+ let(:cask) { Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/basic-cask.rb") }
+ let(:dsl) { Hbc::DSL::Preflight.new(cask, Hbc::FakeSystemCommand) }
+
+ it_behaves_like Hbc::DSL::Base
+
+ it_behaves_like Hbc::Staged do
+ let(:staged) { dsl }
+ end
+end
diff --git a/Library/Homebrew/test/cask/dsl/stanza_proxy_spec.rb b/Library/Homebrew/test/cask/dsl/stanza_proxy_spec.rb
new file mode 100644
index 000000000..2bb7ae633
--- /dev/null
+++ b/Library/Homebrew/test/cask/dsl/stanza_proxy_spec.rb
@@ -0,0 +1,32 @@
+describe Hbc::DSL::StanzaProxy do
+ let(:stanza_proxy) {
+ described_class.new(Array) { [:foo, :bar, :cake] }
+ }
+
+ subject { stanza_proxy }
+ it { is_expected.to be_a_proxy }
+ it { is_expected.to respond_to(:pop) }
+ its(:pop) { is_expected.to eq(:cake) }
+ its(:type) { is_expected.to eq(Array) }
+ its(:to_s) { is_expected.to eq("[:foo, :bar, :cake]") }
+
+ describe "when initialized" do
+ let(:initializing) {
+ proc { |b| described_class.new(Array, &b) }
+ }
+
+ it "does not evaluate the block" do
+ expect(&initializing).not_to yield_control
+ end
+ end
+
+ describe "when receiving a message" do
+ let(:receiving_a_message) {
+ proc { |b| described_class.new(Array, &b).to_s }
+ }
+
+ it "evaluates the block" do
+ expect(&receiving_a_message).to yield_with_no_args
+ end
+ end
+end
diff --git a/Library/Homebrew/test/cask/dsl/uninstall_postflight_spec.rb b/Library/Homebrew/test/cask/dsl/uninstall_postflight_spec.rb
new file mode 100644
index 000000000..f89a181ce
--- /dev/null
+++ b/Library/Homebrew/test/cask/dsl/uninstall_postflight_spec.rb
@@ -0,0 +1,8 @@
+require "test/support/helper/spec/shared_examples/hbc_dsl_base"
+
+describe Hbc::DSL::UninstallPostflight, :cask do
+ let(:cask) { Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/basic-cask.rb") }
+ let(:dsl) { Hbc::DSL::UninstallPostflight.new(cask, Hbc::FakeSystemCommand) }
+
+ it_behaves_like Hbc::DSL::Base
+end
diff --git a/Library/Homebrew/test/cask/dsl/uninstall_preflight_spec.rb b/Library/Homebrew/test/cask/dsl/uninstall_preflight_spec.rb
new file mode 100644
index 000000000..15a0ea156
--- /dev/null
+++ b/Library/Homebrew/test/cask/dsl/uninstall_preflight_spec.rb
@@ -0,0 +1,13 @@
+require "test/support/helper/spec/shared_examples/hbc_dsl_base"
+require "test/support/helper/spec/shared_examples/hbc_staged"
+
+describe Hbc::DSL::UninstallPreflight, :cask do
+ let(:cask) { Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/basic-cask.rb") }
+ let(:dsl) { Hbc::DSL::UninstallPreflight.new(cask, Hbc::FakeSystemCommand) }
+
+ it_behaves_like Hbc::DSL::Base
+
+ it_behaves_like Hbc::Staged do
+ let(:staged) { dsl }
+ end
+end
diff --git a/Library/Homebrew/test/cask/dsl/version_spec.rb b/Library/Homebrew/test/cask/dsl/version_spec.rb
new file mode 100644
index 000000000..acf3db3ab
--- /dev/null
+++ b/Library/Homebrew/test/cask/dsl/version_spec.rb
@@ -0,0 +1,232 @@
+describe Hbc::DSL::Version do
+ shared_examples "expectations hash" do |input_name, expectations|
+ expectations.each do |input_value, expected_output|
+ context "when #{input_name} is #{input_value.inspect}" do
+ let(input_name.to_sym) { input_value }
+ it { is_expected.to eq expected_output }
+ end
+ end
+ end
+
+ shared_examples "version equality" do
+ let(:raw_version) { "1.2.3" }
+
+ context "when other is nil" do
+ let(:other) { nil }
+ it { is_expected.to be false }
+ end
+
+ context "when other is a String" do
+ context "when other == self.raw_version" do
+ let(:other) { "1.2.3" }
+ it { is_expected.to be true }
+ end
+
+ context "when other != self.raw_version" do
+ let(:other) { "1.2.3.4" }
+ it { is_expected.to be false }
+ end
+ end
+
+ context "when other is a #{described_class}" do
+ context "when other.raw_version == self.raw_version" do
+ let(:other) { described_class.new("1.2.3") }
+ it { is_expected.to be true }
+ end
+
+ context "when other.raw_version != self.raw_version" do
+ let(:other) { described_class.new("1.2.3.4") }
+ it { is_expected.to be false }
+ end
+ end
+ end
+
+ let(:version) { described_class.new(raw_version) }
+
+ describe "#==" do
+ subject { version == other }
+ include_examples "version equality"
+ end
+
+ describe "#eql?" do
+ subject { version.eql?(other) }
+ include_examples "version equality"
+ end
+
+ shared_examples "version expectations hash" do |method, hash|
+ subject { version.send(method) }
+ include_examples "expectations hash", :raw_version,
+ { :latest => "latest",
+ "latest" => "latest",
+ "" => "",
+ nil => "" }.merge(hash)
+ end
+
+ describe "#latest?" do
+ include_examples "version expectations hash", :latest?,
+ :latest => true,
+ "latest" => true,
+ "" => false,
+ nil => false,
+ "1.2.3" => false
+ end
+
+ describe "string manipulation helpers" do
+ describe "#major" do
+ include_examples "version expectations hash", :major,
+ "1" => "1",
+ "1.2" => "1",
+ "1.2.3" => "1",
+ "1.2.3_4-5" => "1"
+ end
+
+ describe "#minor" do
+ include_examples "version expectations hash", :minor,
+ "1" => "",
+ "1.2" => "2",
+ "1.2.3" => "2",
+ "1.2.3_4-5" => "2"
+ end
+
+ describe "#patch" do
+ include_examples "version expectations hash", :patch,
+ "1" => "",
+ "1.2" => "",
+ "1.2.3" => "3",
+ "1.2.3_4-5" => "3"
+ end
+
+ describe "#major_minor" do
+ include_examples "version expectations hash", :major_minor,
+ "1" => "1",
+ "1.2" => "1.2",
+ "1.2.3" => "1.2",
+ "1.2.3_4-5" => "1.2"
+ end
+
+ describe "#major_minor_patch" do
+ include_examples "version expectations hash", :major_minor_patch,
+ "1" => "1",
+ "1.2" => "1.2",
+ "1.2.3" => "1.2.3",
+ "1.2.3_4-5" => "1.2.3"
+ end
+
+ describe "#before_comma" do
+ include_examples "version expectations hash", :before_comma,
+ "1.2.3" => "1.2.3",
+ "1.2.3," => "1.2.3",
+ ",abc" => "",
+ "1.2.3,abc" => "1.2.3"
+ end
+
+ describe "#after_comma" do
+ include_examples "version expectations hash", :after_comma,
+ "1.2.3" => "",
+ "1.2.3," => "",
+ ",abc" => "abc",
+ "1.2.3,abc" => "abc"
+ end
+
+ describe "#before_colon" do
+ include_examples "version expectations hash", :before_colon,
+ "1.2.3" => "1.2.3",
+ "1.2.3:" => "1.2.3",
+ ":abc" => "",
+ "1.2.3:abc" => "1.2.3"
+ end
+
+ describe "#after_colon" do
+ include_examples "version expectations hash", :after_colon,
+ "1.2.3" => "",
+ "1.2.3:" => "",
+ ":abc" => "abc",
+ "1.2.3:abc" => "abc"
+ end
+
+ describe "#dots_to_hyphens" do
+ include_examples "version expectations hash", :dots_to_hyphens,
+ "1.2.3_4-5" => "1-2-3_4-5"
+ end
+
+ describe "#dots_to_underscores" do
+ include_examples "version expectations hash", :dots_to_underscores,
+ "1.2.3_4-5" => "1_2_3_4-5"
+ end
+
+ describe "#dots_to_slashes" do
+ include_examples "version expectations hash", :dots_to_slashes,
+ "1.2.3_4-5" => "1/2/3_4-5"
+ end
+
+ describe "#hyphens_to_dots" do
+ include_examples "version expectations hash", :hyphens_to_dots,
+ "1.2.3_4-5" => "1.2.3_4.5"
+ end
+
+ describe "#hyphens_to_underscores" do
+ include_examples "version expectations hash", :hyphens_to_underscores,
+ "1.2.3_4-5" => "1.2.3_4_5"
+ end
+
+ describe "#hyphens_to_slashes" do
+ include_examples "version expectations hash", :hyphens_to_slashes,
+ "1.2.3_4-5" => "1.2.3_4/5"
+ end
+
+ describe "#underscores_to_dots" do
+ include_examples "version expectations hash", :underscores_to_dots,
+ "1.2.3_4-5" => "1.2.3.4-5"
+ end
+
+ describe "#underscores_to_hyphens" do
+ include_examples "version expectations hash", :underscores_to_hyphens,
+ "1.2.3_4-5" => "1.2.3-4-5"
+ end
+
+ describe "#underscores_to_slashes" do
+ include_examples "version expectations hash", :underscores_to_slashes,
+ "1.2.3_4-5" => "1.2.3/4-5"
+ end
+
+ describe "#slashes_to_dots" do
+ include_examples "version expectations hash", :slashes_to_dots,
+ "1.2.3/abc" => "1.2.3.abc"
+ end
+
+ describe "#slashes_to_hyphens" do
+ include_examples "version expectations hash", :slashes_to_hyphens,
+ "1.2.3/abc" => "1.2.3-abc"
+ end
+
+ describe "#slashes_to_underscores" do
+ include_examples "version expectations hash", :slashes_to_underscores,
+ "1.2.3/abc" => "1.2.3_abc"
+ end
+
+ describe "#no_dots" do
+ include_examples "version expectations hash", :no_dots,
+ "1.2.3_4-5" => "123_4-5"
+ end
+
+ describe "#no_hyphens" do
+ include_examples "version expectations hash", :no_hyphens,
+ "1.2.3_4-5" => "1.2.3_45"
+ end
+
+ describe "#no_underscores" do
+ include_examples "version expectations hash", :no_underscores,
+ "1.2.3_4-5" => "1.2.34-5"
+ end
+
+ describe "#no_slashes" do
+ include_examples "version expectations hash", :no_slashes,
+ "1.2.3/abc" => "1.2.3abc"
+ end
+
+ describe "#no_dividers" do
+ include_examples "version expectations hash", :no_dividers,
+ "1.2.3_4-5" => "12345"
+ end
+ end
+end
diff --git a/Library/Homebrew/test/cask/dsl_spec.rb b/Library/Homebrew/test/cask/dsl_spec.rb
new file mode 100644
index 000000000..7872b42a6
--- /dev/null
+++ b/Library/Homebrew/test/cask/dsl_spec.rb
@@ -0,0 +1,546 @@
+describe Hbc::DSL, :cask do
+ let(:cask) { Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/#{token}.rb") }
+ let(:token) { "basic-cask" }
+
+ context "stanzas" do
+ it "lets you set url, homepage, and version" do
+ expect(cask.url.to_s).to eq("http://example.com/TestCask.dmg")
+ expect(cask.homepage).to eq("http://example.com/")
+ expect(cask.version.to_s).to eq("1.2.3")
+ end
+ end
+
+ describe "when a Cask includes an unknown method" do
+ let(:attempt_unknown_method) {
+ lambda do
+ Hbc::Cask.new("unexpected-method-cask") do
+ future_feature :not_yet_on_your_machine
+ end
+ end
+ }
+
+ it "prints a warning that it has encountered an unexpected method" do
+ expected = Regexp.compile(<<-EOS.undent.lines.map(&:chomp).join(""))
+ (?m)
+ Warning:
+ .*
+ Unexpected method 'future_feature' called on Cask unexpected-method-cask\\.
+ .*
+ https://github.com/caskroom/homebrew-cask/blob/master/doc/reporting_bugs/pre_bug_report.md
+ .*
+ https://github.com/caskroom/homebrew-cask#reporting-bugs
+ EOS
+
+ expect {
+ expect(attempt_unknown_method).not_to output.to_stdout
+ }.to output(expected).to_stderr
+ end
+
+ it "will simply warn, not throw an exception" do
+ expect {
+ shutup do
+ attempt_unknown_method.call
+ end
+ }.not_to raise_error
+ end
+ end
+
+ describe "header line" do
+ context "when invalid" do
+ let(:token) { "invalid/invalid-header-format" }
+ it "raises an error" do
+ expect { cask }.to raise_error(SyntaxError)
+ end
+ end
+
+ context "when token does not match the file name" do
+ let(:token) { "invalid/invalid-header-token-mismatch" }
+
+ it "raises an error" do
+ expect {
+ cask
+ }.to raise_error(Hbc::CaskTokenDoesNotMatchError, /Bad header line:.*does not match file name/)
+ end
+ end
+
+ context "when it contains no DSL version" do
+ let(:token) { "no-dsl-version" }
+
+ it "does not require a DSL version in the header" do
+ expect(cask.token).to eq("no-dsl-version")
+ expect(cask.url.to_s).to eq("http://example.com/TestCask.dmg")
+ expect(cask.homepage).to eq("http://example.com/")
+ expect(cask.version.to_s).to eq("1.2.3")
+ end
+ end
+
+ context "when it contains a deprecated DSL version", :needs_compat do
+ let(:token) { "with-dsl-version" }
+
+ it "may use deprecated DSL version hash syntax" do
+ allow(ENV).to receive(:[]).with("HOMEBREW_DEVELOPER").and_return(nil)
+
+ shutup do
+ expect(cask.token).to eq("with-dsl-version")
+ expect(cask.url.to_s).to eq("http://example.com/TestCask.dmg")
+ expect(cask.homepage).to eq("http://example.com/")
+ expect(cask.version.to_s).to eq("1.2.3")
+ end
+ end
+ end
+ end
+
+ describe "name stanza" do
+ it "lets you set the full name via a name stanza" do
+ cask = Hbc::Cask.new("name-cask") do
+ name "Proper Name"
+ end
+
+ expect(cask.name).to eq([
+ "Proper Name",
+ ])
+ end
+
+ it "Accepts an array value to the name stanza" do
+ cask = Hbc::Cask.new("array-name-cask") do
+ name ["Proper Name", "Alternate Name"]
+ end
+
+ expect(cask.name).to eq([
+ "Proper Name",
+ "Alternate Name",
+ ])
+ end
+
+ it "Accepts multiple name stanzas" do
+ cask = Hbc::Cask.new("multi-name-cask") do
+ name "Proper Name"
+ name "Alternate Name"
+ end
+
+ expect(cask.name).to eq([
+ "Proper Name",
+ "Alternate Name",
+ ])
+ end
+ end
+
+ describe "sha256 stanza" do
+ it "lets you set checksum via sha256" do
+ cask = Hbc::Cask.new("checksum-cask") do
+ sha256 "imasha2"
+ end
+
+ expect(cask.sha256).to eq("imasha2")
+ end
+ end
+
+ describe "language stanza" do
+ it "allows multilingual casks" do
+ cask = lambda do
+ Hbc::Cask.new("cask-with-apps") do
+ language "zh" do
+ sha256 "abc123"
+ "zh-CN"
+ end
+
+ language "en-US", default: true do
+ sha256 "xyz789"
+ "en-US"
+ end
+
+ url "https://example.org/#{language}.zip"
+ end
+ end
+
+ allow(MacOS).to receive(:languages).and_return(["zh"])
+ expect(cask.call.language).to eq("zh-CN")
+ expect(cask.call.sha256).to eq("abc123")
+ expect(cask.call.url.to_s).to eq("https://example.org/zh-CN.zip")
+
+ allow(MacOS).to receive(:languages).and_return(["zh-XX"])
+ expect(cask.call.language).to eq("zh-CN")
+ expect(cask.call.sha256).to eq("abc123")
+ expect(cask.call.url.to_s).to eq("https://example.org/zh-CN.zip")
+
+ allow(MacOS).to receive(:languages).and_return(["en"])
+ expect(cask.call.language).to eq("en-US")
+ expect(cask.call.sha256).to eq("xyz789")
+ expect(cask.call.url.to_s).to eq("https://example.org/en-US.zip")
+
+ allow(MacOS).to receive(:languages).and_return(["xx-XX"])
+ expect(cask.call.language).to eq("en-US")
+ expect(cask.call.sha256).to eq("xyz789")
+ expect(cask.call.url.to_s).to eq("https://example.org/en-US.zip")
+
+ allow(MacOS).to receive(:languages).and_return(["xx-XX", "zh", "en"])
+ expect(cask.call.language).to eq("zh-CN")
+ expect(cask.call.sha256).to eq("abc123")
+ expect(cask.call.url.to_s).to eq("https://example.org/zh-CN.zip")
+
+ allow(MacOS).to receive(:languages).and_return(["xx-XX", "en-US", "zh"])
+ expect(cask.call.language).to eq("en-US")
+ expect(cask.call.sha256).to eq("xyz789")
+ expect(cask.call.url.to_s).to eq("https://example.org/en-US.zip")
+ end
+ end
+
+ describe "app stanza" do
+ it "allows you to specify app stanzas" do
+ cask = Hbc::Cask.new("cask-with-apps") do
+ app "Foo.app"
+ app "Bar.app"
+ end
+
+ expect(Array(cask.artifacts[:app])).to eq([["Foo.app"], ["Bar.app"]])
+ end
+
+ it "allow app stanzas to be empty" do
+ cask = Hbc::Cask.new("cask-with-no-apps")
+ expect(Array(cask.artifacts[:app])).to eq([])
+ end
+ end
+
+ describe "caveats stanza" do
+ it "allows caveats to be specified via a method define" do
+ cask = Hbc::Cask.new("plain-cask")
+
+ expect(cask.caveats).to be_empty
+
+ cask = Hbc::Cask.new("cask-with-caveats") do
+ def caveats; <<-EOS.undent
+ When you install this Cask, you probably want to know this.
+ EOS
+ end
+ end
+
+ expect(cask.caveats).to eq("When you install this Cask, you probably want to know this.\n")
+ end
+ end
+
+ describe "pkg stanza" do
+ it "allows installable pkgs to be specified" do
+ cask = Hbc::Cask.new("cask-with-pkgs") do
+ pkg "Foo.pkg"
+ pkg "Bar.pkg"
+ end
+
+ expect(Array(cask.artifacts[:pkg])).to eq([["Foo.pkg"], ["Bar.pkg"]])
+ end
+ end
+
+ describe "url stanza" do
+ let(:token) { "invalid/invalid-two-url" }
+
+ it "prevents defining multiple urls" do
+ expect { cask }.to raise_error(Hbc::CaskInvalidError, /'url' stanza may only appear once/)
+ end
+ end
+
+ describe "homepage stanza" do
+ let(:token) { "invalid/invalid-two-homepage" }
+
+ it "prevents defining multiple homepages" do
+ expect { cask }.to raise_error(Hbc::CaskInvalidError, /'homepage' stanza may only appear once/)
+ end
+ end
+
+ describe "version stanza" do
+ let(:token) { "invalid/invalid-two-version" }
+ it "prevents defining multiple versions" do
+ expect { cask }.to raise_error(Hbc::CaskInvalidError, /'version' stanza may only appear once/)
+ end
+ end
+
+ describe "appcast stanza" do
+ let(:token) { "with-appcast" }
+
+ it "allows appcasts to be specified" do
+ expect(cask.appcast.to_s).to match(/^http/)
+ end
+
+ context "when multiple appcasts are defined" do
+ let(:token) { "invalid/invalid-appcast-multiple" }
+
+ it "raises an error" do
+ expect { cask }.to raise_error(Hbc::CaskInvalidError, /'appcast' stanza may only appear once/)
+ end
+ end
+
+ context "when appcast URL is invalid" do
+ let(:token) { "invalid/invalid-appcast-url" }
+
+ it "refuses to load" do
+ expect { cask }.to raise_error(Hbc::CaskInvalidError)
+ end
+ end
+ end
+
+ describe "GPG stanza" do
+ context "valid" do
+ let(:token) { "with-gpg" }
+
+ it "is allowed to be specified" do
+ expect(cask.gpg.to_s).to match(/\S/)
+ end
+ end
+
+ context "with :key_url" do
+ let(:token) { "with-gpg-key-url" }
+ it "is allowed to be specified" do
+ expect(cask.gpg.to_s).to match(/\S/)
+ end
+ end
+
+ context "specifying mmultiple times" do
+ let(:token) { "invalid/invalid-gpg-multiple-stanzas" }
+
+ it "is not allowed" do
+ expect { cask }.to raise_error(Hbc::CaskInvalidError, /'gpg' stanza may only appear once/)
+ end
+ end
+
+ context "missing GPG key parameters" do
+ let(:token) { "invalid/invalid-gpg-missing-key" }
+
+ it "refuses to load" do
+ expect { cask }.to raise_error(Hbc::CaskInvalidError, /'gpg' stanza must include exactly one/)
+ end
+ end
+
+ context "conflicting GPG key parameters" do
+ let(:token) { "invalid/invalid-gpg-conflicting-keys" }
+
+ it "refuses to load" do
+ expect { cask }.to raise_error(Hbc::CaskInvalidError, /'gpg' stanza must include exactly one/)
+ end
+ end
+
+ context "invalid GPG signature URLs" do
+ let(:token) { "invalid/invalid-gpg-signature-url" }
+
+ it "refuses to load" do
+ expect { cask }.to raise_error(Hbc::CaskInvalidError)
+ end
+ end
+
+ context "invalid GPG key URLs" do
+ let(:token) { "invalid/invalid-gpg-key-url" }
+
+ it "refuses to load" do
+ expect { cask }.to raise_error(Hbc::CaskInvalidError)
+ end
+ end
+
+ context "invalid GPG key IDs" do
+ let(:token) { "invalid/invalid-gpg-key-id" }
+
+ it "refuses to load" do
+ expect { cask }.to raise_error(Hbc::CaskInvalidError)
+ end
+ end
+
+ context "GPG parameter is unknown" do
+ let(:token) { "invalid/invalid-gpg-parameter" }
+
+ it "refuses to load" do
+ expect { cask }.to raise_error(Hbc::CaskInvalidError)
+ end
+ end
+ end
+
+ describe "depends_on stanza" do
+ let(:token) { "invalid/invalid-depends-on-key" }
+
+ it "refuses to load with an invalid depends_on key" do
+ expect { cask }.to raise_error(Hbc::CaskInvalidError)
+ end
+ end
+
+ describe "depends_on formula" do
+ context "with one Formula" do
+ let(:token) { "with-depends-on-formula" }
+
+ it "allows depends_on formula to be specified" do
+ expect(cask.depends_on.formula).not_to be nil
+ end
+ end
+
+ context "with multiple Formulae" do
+ let(:token) { "with-depends-on-formula-multiple" }
+
+ it "allows multiple depends_on formula to be specified" do
+ expect(cask.depends_on.formula).not_to be nil
+ end
+ end
+ end
+
+ describe "depends_on cask" do
+ context "specifying one" do
+ let(:token) { "with-depends-on-cask" }
+ it "is allowed" do
+ expect(cask.depends_on.cask).not_to be nil
+ end
+ end
+
+ context "specifying multiple" do
+ let(:token) { "with-depends-on-cask-multiple" }
+
+ it "is allowed" do
+ expect(cask.depends_on.cask).not_to be nil
+ end
+ end
+ end
+
+ describe "depends_on macos" do
+ context "valid" do
+ let(:token) { "with-depends-on-macos-string" }
+
+ it "allows depends_on macos to be specified" do
+ expect(cask.depends_on.macos).not_to be nil
+ end
+ end
+
+ context "invalid depends_on macos value" do
+ let(:token) { "invalid/invalid-depends-on-macos-bad-release" }
+
+ it "refuses to load" do
+ expect { cask }.to raise_error(Hbc::CaskInvalidError)
+ end
+ end
+
+ context "conflicting depends_on macos forms" do
+ let(:token) { "invalid/invalid-depends-on-macos-conflicting-forms" }
+
+ it "refuses to load" do
+ expect { cask }.to raise_error(Hbc::CaskInvalidError)
+ end
+ end
+ end
+
+ describe "depends_on arch" do
+ context "valid" do
+ let(:token) { "with-depends-on-arch" }
+
+ it "is allowed to be specified" do
+ expect(cask.depends_on.arch).not_to be nil
+ end
+ end
+
+ context "invalid depends_on arch value" do
+ let(:token) { "invalid/invalid-depends-on-arch-value" }
+
+ it "refuses to load" do
+ expect { cask }.to raise_error(Hbc::CaskInvalidError)
+ end
+ end
+ end
+
+ describe "depends_on x11" do
+ context "valid" do
+ let(:token) { "with-depends-on-x11" }
+
+ it "is allowed to be specified" do
+ expect(cask.depends_on.x11).not_to be nil
+ end
+ end
+
+ context "invalid depends_on x11 value" do
+ let(:token) { "invalid/invalid-depends-on-x11-value" }
+
+ it "refuses to load" do
+ expect { cask }.to raise_error(Hbc::CaskInvalidError)
+ end
+ end
+ end
+
+ describe "conflicts_with stanza" do
+ context "valid" do
+ let(:token) { "with-conflicts-with" }
+
+ it "allows conflicts_with stanza to be specified" do
+ expect(cask.conflicts_with.formula).not_to be nil
+ end
+ end
+
+ context "invalid conflicts_with key" do
+ let(:token) { "invalid/invalid-conflicts-with-key" }
+
+ it "refuses to load invalid conflicts_with key" do
+ expect { cask }.to raise_error(Hbc::CaskInvalidError)
+ end
+ end
+ end
+
+ describe "installer stanza" do
+ context "script" do
+ let(:token) { "with-installer-script" }
+
+ it "allows installer script to be specified" do
+ expect(cask.artifacts[:installer].first.script[:executable]).to eq("/usr/bin/true")
+ expect(cask.artifacts[:installer].first.script[:args]).to eq(["--flag"])
+ expect(cask.artifacts[:installer].to_a[1].script[:executable]).to eq("/usr/bin/false")
+ expect(cask.artifacts[:installer].to_a[1].script[:args]).to eq(["--flag"])
+ end
+ end
+
+ context "manual" do
+ let(:token) { "with-installer-manual" }
+
+ it "allows installer manual to be specified" do
+ expect(cask.artifacts[:installer].first.manual).to eq("Caffeine.app")
+ end
+ end
+ end
+
+ describe "stage_only stanza" do
+ context "when there is no other activatable artifact" do
+ let(:token) { "stage-only" }
+
+ it "allows stage_only stanza to be specified" do
+ expect(cask.artifacts[:stage_only].first).to eq([true])
+ end
+ end
+
+ context "when there is are activatable artifacts" do
+ let(:token) { "invalid/invalid-stage-only-conflict" }
+
+ it "prevents specifying stage_only" do
+ expect { cask }.to raise_error(Hbc::CaskInvalidError, /'stage_only' must be the only activatable artifact/)
+ end
+ end
+ end
+
+ describe "auto_updates stanza" do
+ let(:token) { "auto-updates" }
+
+ it "allows auto_updates stanza to be specified" do
+ expect(cask.auto_updates).to be true
+ end
+ end
+
+ describe "appdir" do
+ context "interpolation of the appdir in stanzas" do
+ let(:token) { "appdir-interpolation" }
+
+ it "is allowed" do
+ expect(cask.artifacts[:binary].first).to eq(["#{Hbc.appdir}/some/path"])
+ end
+ end
+
+ it "does not include a trailing slash" do
+ begin
+ original_appdir = Hbc.appdir
+ Hbc.appdir = "#{original_appdir}/"
+
+ cask = Hbc::Cask.new("appdir-trailing-slash") do
+ binary "#{appdir}/some/path"
+ end
+
+ expect(cask.artifacts[:binary].first).to eq(["#{original_appdir}/some/path"])
+ ensure
+ Hbc.appdir = original_appdir
+ end
+ end
+ end
+end
diff --git a/Library/Homebrew/test/cask/installer_spec.rb b/Library/Homebrew/test/cask/installer_spec.rb
new file mode 100644
index 000000000..7dd5b2bda
--- /dev/null
+++ b/Library/Homebrew/test/cask/installer_spec.rb
@@ -0,0 +1,396 @@
+describe Hbc::Installer, :cask do
+ describe "install" do
+ let(:empty_depends_on_stub) {
+ double(formula: [], cask: [], macos: nil, arch: nil, x11: nil)
+ }
+
+ it "downloads and installs a nice fresh Cask" do
+ caffeine = Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/local-caffeine.rb")
+
+ shutup do
+ Hbc::Installer.new(caffeine).install
+ end
+
+ expect(Hbc.caskroom.join("local-caffeine", caffeine.version)).to be_a_directory
+ expect(Hbc.appdir.join("Caffeine.app")).to be_a_directory
+ end
+
+ it "works with dmg-based Casks" do
+ asset = Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/container-dmg.rb")
+
+ shutup do
+ Hbc::Installer.new(asset).install
+ end
+
+ expect(Hbc.caskroom.join("container-dmg", asset.version)).to be_a_directory
+ expect(Hbc.appdir.join("container")).to be_a_file
+ end
+
+ it "works with tar-gz-based Casks" do
+ asset = Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/container-tar-gz.rb")
+
+ shutup do
+ Hbc::Installer.new(asset).install
+ end
+
+ expect(Hbc.caskroom.join("container-tar-gz", asset.version)).to be_a_directory
+ expect(Hbc.appdir.join("container")).to be_a_file
+ end
+
+ it "works with cab-based Casks" do
+ skip("cabextract not installed") if which("cabextract").nil?
+ asset = Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/container-cab.rb")
+
+ allow(asset).to receive(:depends_on).and_return(empty_depends_on_stub)
+
+ shutup do
+ Hbc::Installer.new(asset).install
+ end
+
+ expect(Hbc.caskroom.join("container-cab", asset.version)).to be_a_directory
+ expect(Hbc.appdir.join("container")).to be_a_file
+ end
+
+ it "works with Adobe AIR-based Casks" do
+ skip("Adobe AIR not installed") unless Hbc::Container::Air.installer_exist?
+ asset = Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/container-air.rb")
+
+ shutup do
+ Hbc::Installer.new(asset).install
+ end
+
+ expect(Hbc.caskroom.join("container-air", asset.version)).to be_a_directory
+ expect(Hbc.appdir.join("container.app")).to be_a_directory
+ end
+
+ it "works with 7z-based Casks" do
+ skip("unar not installed") if which("unar").nil?
+ asset = Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/container-7z.rb")
+
+ allow(asset).to receive(:depends_on).and_return(empty_depends_on_stub)
+ shutup do
+ Hbc::Installer.new(asset).install
+ end
+
+ expect(Hbc.caskroom.join("container-7z", asset.version)).to be_a_directory
+ expect(Hbc.appdir.join("container")).to be_a_file
+ end
+
+ it "works with xar-based Casks" do
+ asset = Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/container-xar.rb")
+
+ shutup do
+ Hbc::Installer.new(asset).install
+ end
+
+ expect(Hbc.caskroom.join("container-xar", asset.version)).to be_a_directory
+ expect(Hbc.appdir.join("container")).to be_a_file
+ end
+
+ it "works with Stuffit-based Casks" do
+ skip("unar not installed") if which("unar").nil?
+ asset = Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/container-sit.rb")
+
+ allow(asset).to receive(:depends_on).and_return(empty_depends_on_stub)
+ shutup do
+ Hbc::Installer.new(asset).install
+ end
+
+ expect(Hbc.caskroom.join("container-sit", asset.version)).to be_a_directory
+ expect(Hbc.appdir.join("container")).to be_a_file
+ end
+
+ it "works with RAR-based Casks" do
+ skip("unar not installed") if which("unar").nil?
+ asset = Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/container-rar.rb")
+
+ allow(asset).to receive(:depends_on).and_return(empty_depends_on_stub)
+ shutup do
+ Hbc::Installer.new(asset).install
+ end
+
+ expect(Hbc.caskroom.join("container-rar", asset.version)).to be_a_directory
+ expect(Hbc.appdir.join("container")).to be_a_file
+ end
+
+ it "works with pure bzip2-based Casks" do
+ asset = Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/container-bzip2.rb")
+
+ shutup do
+ Hbc::Installer.new(asset).install
+ end
+
+ expect(Hbc.caskroom.join("container-bzip2", asset.version)).to be_a_directory
+ expect(Hbc.appdir.join("container-bzip2--#{asset.version}")).to be_a_file
+ end
+
+ it "works with pure gzip-based Casks" do
+ asset = Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/container-gzip.rb")
+
+ shutup do
+ Hbc::Installer.new(asset).install
+ end
+
+ expect(Hbc.caskroom.join("container-gzip", asset.version)).to be_a_directory
+ expect(Hbc.appdir.join("container")).to be_a_file
+ end
+
+ it "works with pure xz-based Casks" do
+ skip("unxz not installed") if which("unxz").nil?
+ asset = Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/container-xz.rb")
+
+ allow(asset).to receive(:depends_on).and_return(empty_depends_on_stub)
+ shutup do
+ Hbc::Installer.new(asset).install
+ end
+
+ expect(Hbc.caskroom.join("container-xz", asset.version)).to be_a_directory
+ expect(Hbc.appdir.join("container-xz--#{asset.version}")).to be_a_file
+ end
+
+ it "works with lzma-based Casks" do
+ skip("unlzma not installed") if which("unlzma").nil?
+ asset = Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/container-lzma.rb")
+
+ allow(asset).to receive(:depends_on).and_return(empty_depends_on_stub)
+ shutup do
+ Hbc::Installer.new(asset).install
+ end
+
+ expect(Hbc.caskroom.join("container-lzma", asset.version)).to be_a_directory
+ expect(Hbc.appdir.join("container-lzma--#{asset.version}")).to be_a_file
+ end
+
+ it "blows up on a bad checksum" do
+ bad_checksum = Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/bad-checksum.rb")
+ expect {
+ shutup do
+ Hbc::Installer.new(bad_checksum).install
+ end
+ }.to raise_error(Hbc::CaskSha256MismatchError)
+ end
+
+ it "blows up on a missing checksum" do
+ missing_checksum = Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/missing-checksum.rb")
+ expect {
+ shutup do
+ Hbc::Installer.new(missing_checksum).install
+ end
+ }.to raise_error(Hbc::CaskSha256MissingError)
+ end
+
+ it "installs fine if sha256 :no_check is used" do
+ no_checksum = Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/no-checksum.rb")
+
+ shutup do
+ Hbc::Installer.new(no_checksum).install
+ end
+
+ expect(no_checksum).to be_installed
+ end
+
+ it "fails to install if sha256 :no_check is used with --require-sha" do
+ no_checksum = Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/no-checksum.rb")
+ expect {
+ Hbc::Installer.new(no_checksum, require_sha: true).install
+ }.to raise_error(Hbc::CaskNoShasumError)
+ end
+
+ it "installs fine if sha256 :no_check is used with --require-sha and --force" do
+ no_checksum = Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/no-checksum.rb")
+
+ shutup do
+ Hbc::Installer.new(no_checksum, require_sha: true, force: true).install
+ end
+
+ expect(no_checksum).to be_installed
+ end
+
+ it "prints caveats if they're present" do
+ with_caveats = Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/with-caveats.rb")
+
+ expect {
+ Hbc::Installer.new(with_caveats).install
+ }.to output(/Here are some things you might want to know/).to_stdout
+
+ expect(with_caveats).to be_installed
+ end
+
+ it "prints installer :manual instructions when present" do
+ with_installer_manual = Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/with-installer-manual.rb")
+
+ expect {
+ Hbc::Installer.new(with_installer_manual).install
+ }.to output(/To complete the installation of Cask with-installer-manual, you must also\nrun the installer at\n\n '#{with_installer_manual.staged_path.join('Caffeine.app')}'/).to_stdout
+
+ expect(with_installer_manual).to be_installed
+ end
+
+ it "does not extract __MACOSX directories from zips" do
+ with_macosx_dir = Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/with-macosx-dir.rb")
+
+ shutup do
+ Hbc::Installer.new(with_macosx_dir).install
+ end
+
+ expect(with_macosx_dir.staged_path.join("__MACOSX")).not_to be_a_directory
+ end
+
+ it "installer method raises an exception when already-installed Casks which auto-update are attempted" do
+ with_auto_updates = Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/auto-updates.rb")
+
+ expect(with_auto_updates).not_to be_installed
+
+ installer = Hbc::Installer.new(with_auto_updates)
+
+ shutup do
+ installer.install
+ end
+
+ expect {
+ installer.install
+ }.to raise_error(Hbc::CaskAlreadyInstalledAutoUpdatesError)
+ end
+
+ it "allows already-installed Casks which auto-update to be installed if force is provided" do
+ with_auto_updates = Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/auto-updates.rb")
+
+ expect(with_auto_updates).not_to be_installed
+
+ shutup do
+ Hbc::Installer.new(with_auto_updates).install
+ end
+
+ expect {
+ shutup do
+ Hbc::Installer.new(with_auto_updates, force: true).install
+ end
+ }.not_to raise_error
+ end
+
+ # unlike the CLI, the internal interface throws exception on double-install
+ it "installer method raises an exception when already-installed Casks are attempted" do
+ transmission = Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/local-transmission.rb")
+
+ expect(transmission).not_to be_installed
+
+ installer = Hbc::Installer.new(transmission)
+
+ shutup do
+ installer.install
+ end
+
+ expect {
+ installer.install
+ }.to raise_error(Hbc::CaskAlreadyInstalledError)
+ end
+
+ it "allows already-installed Casks to be installed if force is provided" do
+ transmission = Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/local-transmission.rb")
+
+ expect(transmission).not_to be_installed
+
+ shutup do
+ Hbc::Installer.new(transmission).install
+ end
+
+ shutup do
+ Hbc::Installer.new(transmission, force: true).install
+ end # wont_raise
+ end
+
+ it "works naked-pkg-based Casks" do
+ naked_pkg = Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/container-pkg.rb")
+
+ shutup do
+ Hbc::Installer.new(naked_pkg).install
+ end
+
+ expect(Hbc.caskroom.join("container-pkg", naked_pkg.version, "container.pkg")).to be_a_file
+ end
+
+ it "works properly with an overridden container :type" do
+ naked_executable = Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/naked-executable.rb")
+
+ shutup do
+ Hbc::Installer.new(naked_executable).install
+ end
+
+ expect(Hbc.caskroom.join("naked-executable", naked_executable.version, "naked_executable")).to be_a_file
+ end
+
+ it "works fine with a nested container" do
+ nested_app = Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/nested-app.rb")
+
+ shutup do
+ Hbc::Installer.new(nested_app).install
+ end
+
+ expect(Hbc.appdir.join("MyNestedApp.app")).to be_a_directory
+ end
+
+ it "generates and finds a timestamped metadata directory for an installed Cask" do
+ caffeine = Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/local-caffeine.rb")
+
+ shutup do
+ Hbc::Installer.new(caffeine).install
+ end
+
+ m_path = caffeine.metadata_path(:now, true)
+ expect(caffeine.metadata_path(:now, false)).to eq(m_path)
+ expect(caffeine.metadata_path(:latest)).to eq(m_path)
+ end
+
+ it "generates and finds a metadata subdirectory for an installed Cask" do
+ caffeine = Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/local-caffeine.rb")
+
+ shutup do
+ Hbc::Installer.new(caffeine).install
+ end
+
+ subdir_name = "Casks"
+ m_subdir = caffeine.metadata_subdir(subdir_name, :now, true)
+ expect(caffeine.metadata_subdir(subdir_name, :now, false)).to eq(m_subdir)
+ expect(caffeine.metadata_subdir(subdir_name, :latest)).to eq(m_subdir)
+ end
+ end
+
+ describe "uninstall" do
+ it "fully uninstalls a Cask" do
+ caffeine = Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/local-caffeine.rb")
+ installer = Hbc::Installer.new(caffeine)
+
+ shutup do
+ installer.install
+ installer.uninstall
+ end
+
+ expect(Hbc.caskroom.join("local-caffeine", caffeine.version, "Caffeine.app")).not_to be_a_directory
+ expect(Hbc.caskroom.join("local-caffeine", caffeine.version)).not_to be_a_directory
+ expect(Hbc.caskroom.join("local-caffeine")).not_to be_a_directory
+ end
+
+ it "uninstalls all versions if force is set" do
+ caffeine = Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/local-caffeine.rb")
+ mutated_version = caffeine.version + ".1"
+
+ shutup do
+ Hbc::Installer.new(caffeine).install
+ end
+
+ expect(Hbc.caskroom.join("local-caffeine", caffeine.version)).to be_a_directory
+ expect(Hbc.caskroom.join("local-caffeine", mutated_version)).not_to be_a_directory
+ FileUtils.mv(Hbc.caskroom.join("local-caffeine", caffeine.version), Hbc.caskroom.join("local-caffeine", mutated_version))
+ expect(Hbc.caskroom.join("local-caffeine", caffeine.version)).not_to be_a_directory
+ expect(Hbc.caskroom.join("local-caffeine", mutated_version)).to be_a_directory
+
+ shutup do
+ Hbc::Installer.new(caffeine, force: true).uninstall
+ end
+
+ expect(Hbc.caskroom.join("local-caffeine", caffeine.version)).not_to be_a_directory
+ expect(Hbc.caskroom.join("local-caffeine", mutated_version)).not_to be_a_directory
+ expect(Hbc.caskroom.join("local-caffeine")).not_to be_a_directory
+ end
+ end
+end
diff --git a/Library/Homebrew/test/cask/macos_spec.rb b/Library/Homebrew/test/cask/macos_spec.rb
new file mode 100644
index 000000000..f931e1104
--- /dev/null
+++ b/Library/Homebrew/test/cask/macos_spec.rb
@@ -0,0 +1,67 @@
+describe MacOS do
+ it "says '/' is undeletable" do
+ expect(MacOS).to be_undeletable(
+ "/",
+ )
+ expect(MacOS).to be_undeletable(
+ "/.",
+ )
+ expect(MacOS).to be_undeletable(
+ "/usr/local/Library/Taps/../../../..",
+ )
+ end
+
+ it "says '/Applications' is undeletable" do
+ expect(MacOS).to be_undeletable(
+ "/Applications",
+ )
+ expect(MacOS).to be_undeletable(
+ "/Applications/",
+ )
+ expect(MacOS).to be_undeletable(
+ "/Applications/.",
+ )
+ expect(MacOS).to be_undeletable(
+ "/Applications/Mail.app/..",
+ )
+ end
+
+ it "says the home directory is undeletable" do
+ expect(MacOS).to be_undeletable(
+ Dir.home,
+ )
+ expect(MacOS).to be_undeletable(
+ "#{Dir.home}/",
+ )
+ expect(MacOS).to be_undeletable(
+ "#{Dir.home}/Documents/..",
+ )
+ end
+
+ it "says the user library directory is undeletable" do
+ expect(MacOS).to be_undeletable(
+ "#{Dir.home}/Library",
+ )
+ expect(MacOS).to be_undeletable(
+ "#{Dir.home}/Library/",
+ )
+ expect(MacOS).to be_undeletable(
+ "#{Dir.home}/Library/.",
+ )
+ expect(MacOS).to be_undeletable(
+ "#{Dir.home}/Library/Preferences/..",
+ )
+ end
+
+ it "says '/Applications/.app' is deletable" do
+ expect(MacOS).not_to be_undeletable(
+ "/Applications/.app",
+ )
+ end
+
+ it "says '/Applications/SnakeOil Professional.app' is deletable" do
+ expect(MacOS).not_to be_undeletable(
+ "/Applications/SnakeOil Professional.app",
+ )
+ end
+end
diff --git a/Library/Homebrew/test/cask/pkg_spec.rb b/Library/Homebrew/test/cask/pkg_spec.rb
new file mode 100644
index 000000000..78a2eb75e
--- /dev/null
+++ b/Library/Homebrew/test/cask/pkg_spec.rb
@@ -0,0 +1,112 @@
+describe Hbc::Pkg, :cask do
+ describe "uninstall" do
+ let(:fake_system_command) { Hbc::NeverSudoSystemCommand }
+ let(:empty_response) { double(stdout: "") }
+ let(:pkg) { described_class.new("my.fake.pkg", fake_system_command) }
+
+ it "removes files and dirs referenced by the pkg" do
+ some_files = Array.new(3) { Pathname.new(Tempfile.new("testfile").path) }
+ allow(pkg).to receive(:pkgutil_bom_files).and_return(some_files)
+
+ some_specials = Array.new(3) { Pathname.new(Tempfile.new("testfile").path) }
+ allow(pkg).to receive(:pkgutil_bom_specials).and_return(some_specials)
+
+ some_dirs = Array.new(3) { Pathname.new(Dir.mktmpdir) }
+ allow(pkg).to receive(:pkgutil_bom_dirs).and_return(some_dirs)
+
+ allow(pkg).to receive(:forget)
+
+ pkg.uninstall
+
+ some_files.each do |file|
+ expect(file).not_to exist
+ end
+
+ some_dirs.each do |dir|
+ expect(dir).not_to exist
+ end
+ end
+
+ context "pkgutil" do
+ let(:fake_system_command) { class_double(Hbc::SystemCommand) }
+
+ it "forgets the pkg" do
+ allow(fake_system_command).to receive(:run!).with(
+ "/usr/sbin/pkgutil",
+ args: ["--only-files", "--files", "my.fake.pkg"],
+ ).and_return(empty_response)
+
+ allow(fake_system_command).to receive(:run!).with(
+ "/usr/sbin/pkgutil",
+ args: ["--only-dirs", "--files", "my.fake.pkg"],
+ ).and_return(empty_response)
+
+ allow(fake_system_command).to receive(:run!).with(
+ "/usr/sbin/pkgutil",
+ args: ["--files", "my.fake.pkg"],
+ ).and_return(empty_response)
+
+ expect(fake_system_command).to receive(:run!).with(
+ "/usr/sbin/pkgutil",
+ args: ["--forget", "my.fake.pkg"],
+ sudo: true,
+ )
+
+ pkg.uninstall
+ end
+ end
+
+ it "removes broken symlinks" do
+ fake_dir = Pathname.new(Dir.mktmpdir)
+ fake_file = fake_dir.join("ima_file").tap { |path| FileUtils.touch(path) }
+
+ intact_symlink = fake_dir.join("intact_symlink").tap { |path| path.make_symlink(fake_file) }
+ broken_symlink = fake_dir.join("broken_symlink").tap { |path| path.make_symlink("im_nota_file") }
+
+ allow(pkg).to receive(:pkgutil_bom_specials).and_return([])
+ allow(pkg).to receive(:pkgutil_bom_files).and_return([])
+ allow(pkg).to receive(:pkgutil_bom_dirs).and_return([fake_dir])
+ allow(pkg).to receive(:forget)
+
+ pkg.uninstall
+
+ expect(intact_symlink).to exist
+ expect(broken_symlink).not_to exist
+ expect(fake_dir).to exist
+ end
+
+ it "removes files incorrectly reportes as directories" do
+ fake_dir = Pathname.new(Dir.mktmpdir)
+ fake_file = fake_dir.join("ima_file_pretending_to_be_a_dir").tap { |path| FileUtils.touch(path) }
+
+ allow(pkg).to receive(:pkgutil_bom_specials).and_return([])
+ allow(pkg).to receive(:pkgutil_bom_files).and_return([])
+ allow(pkg).to receive(:pkgutil_bom_dirs).and_return([fake_file, fake_dir])
+ allow(pkg).to receive(:forget)
+
+ pkg.uninstall
+
+ expect(fake_file).not_to exist
+ expect(fake_dir).not_to exist
+ end
+
+ it "snags permissions on ornery dirs, but returns them afterwards" do
+ fake_dir = Pathname.new(Dir.mktmpdir)
+ fake_file = fake_dir.join("ima_installed_file").tap { |path| FileUtils.touch(path) }
+ fake_dir.chmod(0000)
+
+ allow(pkg).to receive(:pkgutil_bom_specials).and_return([])
+ allow(pkg).to receive(:pkgutil_bom_files).and_return([fake_file])
+ allow(pkg).to receive(:pkgutil_bom_dirs).and_return([fake_dir])
+ allow(pkg).to receive(:forget)
+
+ shutup do
+ pkg.uninstall
+ end
+
+ expect(fake_dir).to be_a_directory
+ expect(fake_file).not_to be_a_file
+ expect((fake_dir.stat.mode % 01000).to_s(8)).to eq("0")
+ end
+ end
+end
diff --git a/Library/Homebrew/test/cask/scopes_spec.rb b/Library/Homebrew/test/cask/scopes_spec.rb
new file mode 100644
index 000000000..330683b2a
--- /dev/null
+++ b/Library/Homebrew/test/cask/scopes_spec.rb
@@ -0,0 +1,37 @@
+describe Hbc::Scopes, :cask do
+ describe "installed" do
+ it "returns a list installed Casks by loading Casks for all the dirs that exist in the caskroom" do
+ allow(Hbc).to receive(:load) { |token| "loaded-#{token}" }
+
+ Hbc.caskroom.join("cask-bar").mkpath
+ Hbc.caskroom.join("cask-foo").mkpath
+
+ installed_casks = Hbc.installed
+
+ expect(Hbc).to have_received(:load).with("cask-bar")
+ expect(Hbc).to have_received(:load).with("cask-foo")
+ expect(installed_casks).to eq(
+ %w[
+ loaded-cask-bar
+ loaded-cask-foo
+ ],
+ )
+ end
+
+ it "optimizes performance by resolving to a fully qualified path before calling Hbc.load" do
+ fake_tapped_cask_dir = Pathname.new(Dir.mktmpdir).join("Casks")
+ absolute_path_to_cask = fake_tapped_cask_dir.join("some-cask.rb")
+
+ allow(Hbc).to receive(:load)
+ allow(Hbc).to receive(:all_tapped_cask_dirs) { [fake_tapped_cask_dir] }
+
+ Hbc.caskroom.join("some-cask").mkdir
+ fake_tapped_cask_dir.mkdir
+ FileUtils.touch(absolute_path_to_cask)
+
+ Hbc.installed
+
+ expect(Hbc).to have_received(:load).with(absolute_path_to_cask)
+ end
+ end
+end
diff --git a/Library/Homebrew/test/cask/staged_spec.rb b/Library/Homebrew/test/cask/staged_spec.rb
new file mode 100644
index 000000000..670775b7a
--- /dev/null
+++ b/Library/Homebrew/test/cask/staged_spec.rb
@@ -0,0 +1,16 @@
+# TODO: this test should be named after the corresponding class, once
+# that class is abstracted from installer.rb. It makes little sense
+# to be invoking bundle_identifier off of the installer instance.
+describe "Operations on staged Casks", :cask do
+ describe "bundle ID" do
+ let(:cask) { Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/local-transmission.rb") }
+ let(:installer) { Hbc::Installer.new(cask) }
+ it "fetches the bundle ID from a staged cask" do
+ shutup do
+ installer.install
+ end
+
+ expect(installer.bundle_identifier).to eq("org.m0k.transmission")
+ end
+ end
+end
diff --git a/Library/Homebrew/test/cask/system_command_result_spec.rb b/Library/Homebrew/test/cask/system_command_result_spec.rb
new file mode 100644
index 000000000..4a077de7b
--- /dev/null
+++ b/Library/Homebrew/test/cask/system_command_result_spec.rb
@@ -0,0 +1,95 @@
+require "hbc/system_command"
+
+describe Hbc::SystemCommand::Result, :cask do
+ describe "::_parse_plist" do
+ subject { described_class._parse_plist(command, input) }
+ let(:command) { Hbc::SystemCommand.new("/usr/bin/true", {}) }
+ let(:plist) {
+ <<-EOS.undent
+ <?xml version="1.0" encoding="UTF-8"?>
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+ <plist version="1.0">
+ <dict>
+ <key>system-entities</key>
+ <array>
+ <dict>
+ <key>content-hint</key>
+ <string>Apple_partition_map</string>
+ <key>dev-entry</key>
+ <string>/dev/disk3s1</string>
+ <key>potentially-mountable</key>
+ <false/>
+ <key>unmapped-content-hint</key>
+ <string>Apple_partition_map</string>
+ </dict>
+ <dict>
+ <key>content-hint</key>
+ <string>Apple_partition_scheme</string>
+ <key>dev-entry</key>
+ <string>/dev/disk3</string>
+ <key>potentially-mountable</key>
+ <false/>
+ <key>unmapped-content-hint</key>
+ <string>Apple_partition_scheme</string>
+ </dict>
+ <dict>
+ <key>content-hint</key>
+ <string>Apple_HFS</string>
+ <key>dev-entry</key>
+ <string>/dev/disk3s2</string>
+ <key>mount-point</key>
+ <string>/private/tmp/dmg.BhfS2g</string>
+ <key>potentially-mountable</key>
+ <true/>
+ <key>unmapped-content-hint</key>
+ <string>Apple_HFS</string>
+ <key>volume-kind</key>
+ <string>hfs</string>
+ </dict>
+ </array>
+ </dict>
+ </plist>
+ EOS
+ }
+
+ context "when output contains garbage" do
+ let(:input) {
+ <<-EOS.undent
+ Hello there! I am in no way XML am I?!?!
+
+ That's a little silly... you were expexting XML here!
+
+ What is a parser to do?
+
+ Hopefully <not> explode!
+
+ #{plist}
+ EOS
+ }
+
+ it "ignores garbage before xml" do
+ expect(subject.keys).to eq(["system-entities"])
+ expect(subject["system-entities"].length).to eq(3)
+ end
+ end
+
+ context "given a hdiutil output as input" do
+ let(:input) { plist }
+
+ it "successfully parses it" do
+ expect(subject.keys).to eq(["system-entities"])
+ expect(subject["system-entities"].length).to eq(3)
+ expect(subject["system-entities"].map { |e| e["dev-entry"] })
+ .to eq(["/dev/disk3s1", "/dev/disk3", "/dev/disk3s2"])
+ end
+ end
+
+ context "given an empty input" do
+ let(:input) { "" }
+
+ it "raises an error" do
+ expect { subject }.to raise_error(Hbc::CaskError, /Empty plist input/)
+ end
+ end
+ end
+end
diff --git a/Library/Homebrew/test/cask/system_command_spec.rb b/Library/Homebrew/test/cask/system_command_spec.rb
new file mode 100644
index 000000000..8d1180bea
--- /dev/null
+++ b/Library/Homebrew/test/cask/system_command_spec.rb
@@ -0,0 +1,139 @@
+describe Hbc::SystemCommand, :cask do
+ describe "when the exit code is 0" do
+ describe "its result" do
+ subject { described_class.run("/usr/bin/true") }
+
+ it { is_expected.to be_a_success }
+ its(:exit_status) { is_expected.to eq(0) }
+ end
+ end
+
+ describe "when the exit code is 1" do
+ let(:command) { "/usr/bin/false" }
+
+ describe "and the command must succeed" do
+ it "throws an error" do
+ expect {
+ described_class.run!(command)
+ }.to raise_error(Hbc::CaskCommandFailedError)
+ end
+ end
+
+ describe "and the command does not have to succeed" do
+ describe "its result" do
+ subject { described_class.run(command) }
+
+ it { is_expected.not_to be_a_success }
+ its(:exit_status) { is_expected.to eq(1) }
+ end
+ end
+ end
+
+ describe "given a pathname" do
+ let(:command) { "/bin/ls" }
+ let(:path) { Pathname(Dir.mktmpdir) }
+
+ before do
+ FileUtils.touch(path.join("somefile"))
+ end
+
+ describe "its result" do
+ subject { described_class.run(command, args: [path]) }
+
+ it { is_expected.to be_a_success }
+ its(:stdout) { is_expected.to eq("somefile\n") }
+ end
+ end
+
+ describe "with both STDOUT and STDERR output from upstream" do
+ let(:command) { "/bin/bash" }
+ let(:options) {
+ { args: [
+ "-c",
+ "for i in $(seq 1 2 5); do echo $i; echo $(($i + 1)) >&2; done",
+ ] }
+ }
+
+ shared_examples "it returns '1 2 3 4 5 6'" do
+ describe "its result" do
+ subject { shutup { described_class.run(command, options) } }
+
+ it { is_expected.to be_a_success }
+ its(:stdout) { is_expected.to eq([1, 3, 5, nil].join("\n")) }
+ its(:stderr) { is_expected.to eq([2, 4, 6, nil].join("\n")) }
+ end
+ end
+
+ describe "with default options" do
+ it "echoes only STDERR" do
+ expected = [2, 4, 6].map { |i| "==> #{i}\n" }.join("")
+ expect {
+ described_class.run(command, options)
+ }.to output(expected).to_stdout
+ end
+
+ include_examples("it returns '1 2 3 4 5 6'")
+ end
+
+ describe "with print_stdout" do
+ before do
+ options.merge!(print_stdout: true)
+ end
+
+ it "echoes both STDOUT and STDERR" do
+ (1..6).each do |i|
+ expect {
+ described_class.run(command, options)
+ }.to output(/==> #{ i }/).to_stdout
+ end
+ end
+
+ include_examples("it returns '1 2 3 4 5 6'")
+ end
+
+ describe "without print_stderr" do
+ before do
+ options.merge!(print_stderr: false)
+ end
+
+ it "echoes nothing" do
+ expect {
+ described_class.run(command, options)
+ }.to output("").to_stdout
+ end
+
+ include_examples("it returns '1 2 3 4 5 6'")
+ end
+
+ describe "with print_stdout but without print_stderr" do
+ before do
+ options.merge!(print_stdout: true, print_stderr: false)
+ end
+
+ it "echoes only STDOUT" do
+ expected = [1, 3, 5].map { |i| "==> #{i}\n" }.join("")
+ expect {
+ described_class.run(command, options)
+ }.to output(expected).to_stdout
+ end
+
+ include_examples("it returns '1 2 3 4 5 6'")
+ end
+ end
+
+ describe "with a very long STDERR output" do
+ let(:command) { "/bin/bash" }
+ let(:options) {
+ { args: [
+ "-c",
+ "for i in $(seq 1 2 100000); do echo $i; echo $(($i + 1)) >&2; done",
+ ] }
+ }
+
+ it "returns without deadlocking" do
+ wait(15).for {
+ shutup { described_class.run(command, options) }
+ }.to be_a_success
+ end
+ end
+end
diff --git a/Library/Homebrew/test/cask/underscore_supporting_uri_spec.rb b/Library/Homebrew/test/cask/underscore_supporting_uri_spec.rb
new file mode 100644
index 000000000..49d3ea63f
--- /dev/null
+++ b/Library/Homebrew/test/cask/underscore_supporting_uri_spec.rb
@@ -0,0 +1,14 @@
+describe Hbc::UnderscoreSupportingURI, :cask do
+ describe "parse" do
+ it "works like normal on normal URLs" do
+ uri = Hbc::UnderscoreSupportingURI.parse("http://example.com/TestCask.dmg")
+ expect(uri).to eq(URI("http://example.com/TestCask.dmg"))
+ end
+
+ it "works just fine on URIs with underscores" do
+ uri = Hbc::UnderscoreSupportingURI.parse("http://dl_dir.qq.com/qqfile/qq/QQforMac/QQ_V3.0.0.dmg")
+ expect(uri.host).to include("_")
+ expect(uri.to_s).to eq("http://dl_dir.qq.com/qqfile/qq/QQforMac/QQ_V3.0.0.dmg")
+ end
+ end
+end
diff --git a/Library/Homebrew/test/cask/url_checker_spec.rb b/Library/Homebrew/test/cask/url_checker_spec.rb
new file mode 100644
index 000000000..c505d2cb4
--- /dev/null
+++ b/Library/Homebrew/test/cask/url_checker_spec.rb
@@ -0,0 +1,42 @@
+describe Hbc::UrlChecker, :cask do
+ describe "request processing" do
+ let(:cask) { Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/basic-cask.rb") }
+ let(:checker) { Hbc::UrlChecker.new(cask) }
+
+ before(:each) do
+ allow(Hbc::Fetcher).to receive(:head).and_return(response)
+ checker.run
+ end
+
+ context "with an empty response" do
+ let(:response) { "" }
+
+ it "adds an error" do
+ expect(checker.errors).to include("timeout while requesting #{cask.url}")
+ end
+ end
+
+ context "with a valid http response" do
+ let(:response) {
+ <<-EOS.undent
+ HTTP/1.1 200 OK
+ Content-Type: application/x-apple-diskimage
+ ETag: "b4208f3e84967be4b078ecaa03fba941"
+ Content-Length: 23726161
+ Last-Modified: Sun, 12 Aug 2012 21:17:21 GMT
+ EOS
+ }
+
+ it "properly populates the response code and headers" do
+ expect(checker.errors).to be_empty
+ expect(checker.response_status).to eq("HTTP/1.1 200 OK")
+ expect(checker.headers).to eq(
+ "Content-Type" => "application/x-apple-diskimage",
+ "ETag" => '"b4208f3e84967be4b078ecaa03fba941"',
+ "Content-Length" => "23726161",
+ "Last-Modified" => "Sun, 12 Aug 2012 21:17:21 GMT",
+ )
+ end
+ end
+ end
+end
diff --git a/Library/Homebrew/test/cask/verify/checksum_spec.rb b/Library/Homebrew/test/cask/verify/checksum_spec.rb
new file mode 100644
index 000000000..d803c566d
--- /dev/null
+++ b/Library/Homebrew/test/cask/verify/checksum_spec.rb
@@ -0,0 +1,91 @@
+describe Hbc::Verify::Checksum do
+ let(:cask) { double("cask") }
+ let(:downloaded_path) { double("downloaded_path") }
+ let(:verification) { described_class.new(cask, downloaded_path) }
+
+ before do
+ allow(cask).to receive(:sha256).and_return(sha256)
+ end
+
+ around do |example|
+ shutup { example.run }
+ end
+
+ describe ".me?" do
+ subject { described_class.me?(cask) }
+
+ context "sha256 is :no_check" do
+ let(:sha256) { :no_check }
+
+ it { is_expected.to be false }
+ end
+
+ context "sha256 is nil" do
+ let(:sha256) { nil }
+
+ it { is_expected.to be true }
+ end
+
+ context "sha256 is empty" do
+ let(:sha256) { "" }
+
+ it { is_expected.to be true }
+ end
+
+ context "sha256 is a valid shasum" do
+ let(:sha256) { "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef" }
+
+ it { is_expected.to be true }
+ end
+ end
+
+ describe "#verify" do
+ subject { verification.verify }
+
+ let(:computed) { "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef" }
+
+ before do
+ allow(verification).to receive(:computed).and_return(computed)
+ end
+
+ context "sha256 matches computed" do
+ let(:sha256) { computed }
+
+ it "does not raise an error" do
+ expect { subject }.not_to raise_error
+ end
+ end
+
+ context "sha256 is :no_check" do
+ let(:sha256) { :no_check }
+
+ it "does not raise an error" do
+ expect { subject }.not_to raise_error
+ end
+ end
+
+ context "sha256 does not match computed" do
+ let(:sha256) { "d3adb33fd3adb33fd3adb33fd3adb33fd3adb33fd3adb33fd3adb33fd3adb33f" }
+
+ it "raises an error" do
+ expect { subject }.to raise_error(Hbc::CaskSha256MismatchError)
+ end
+ end
+
+ context "sha256 is nil" do
+ let(:sha256) { nil }
+
+ it "raises an error" do
+ expect { subject }.to raise_error(Hbc::CaskSha256MissingError)
+ end
+ end
+
+ context "sha256 is empty" do
+ let(:sha256) { "" }
+
+ it "raises an error" do
+ expect { subject }.to raise_error(Hbc::CaskSha256MissingError)
+ end
+ end
+ end
+end
diff --git a/Library/Homebrew/test/cask/verify_spec.rb b/Library/Homebrew/test/cask/verify_spec.rb
new file mode 100644
index 000000000..5d95fb3a2
--- /dev/null
+++ b/Library/Homebrew/test/cask/verify_spec.rb
@@ -0,0 +1,63 @@
+describe Hbc::Verify, :cask do
+ let(:cask) { double("cask") }
+
+ let(:verification_classes) {
+ [
+ applicable_verification_class,
+ inapplicable_verification_class,
+ ]
+ }
+
+ let(:applicable_verification_class) {
+ double("applicable_verification_class", me?: true)
+ }
+
+ let(:inapplicable_verification_class) {
+ double("inapplicable_verification_class", me?: false)
+ }
+
+ before do
+ allow(described_class).to receive(:verifications)
+ .and_return(verification_classes)
+ end
+
+ describe ".for_cask" do
+ subject { described_class.for_cask(cask) }
+
+ it "checks applicability of each verification" do
+ verification_classes.each do |verify_class|
+ expect(verify_class).to receive(:me?).with(cask)
+ end
+ subject
+ end
+
+ it "includes applicable verifications" do
+ expect(subject).to include(applicable_verification_class)
+ end
+
+ it "excludes inapplicable verifications" do
+ expect(subject).not_to include(inapplicable_verification_class)
+ end
+ end
+
+ describe ".all" do
+ let(:downloaded_path) { double("downloaded_path") }
+ let(:applicable_verification) { double("applicable_verification") }
+ let(:inapplicable_verification) { double("inapplicable_verification") }
+
+ subject { described_class.all(cask, downloaded_path) }
+
+ before do
+ allow(applicable_verification_class).to receive(:new)
+ .and_return(applicable_verification)
+ allow(inapplicable_verification_class).to receive(:new)
+ .and_return(inapplicable_verification)
+ end
+
+ it "runs only applicable verifications" do
+ expect(applicable_verification).to receive(:verify)
+ expect(inapplicable_verification).not_to receive(:verify)
+ subject
+ end
+ end
+end
diff --git a/Library/Homebrew/test/spec_helper.rb b/Library/Homebrew/test/spec_helper.rb
index 122aaba46..4616d7708 100644
--- a/Library/Homebrew/test/spec_helper.rb
+++ b/Library/Homebrew/test/spec_helper.rb
@@ -18,6 +18,8 @@ require "test/support/helper/shutup"
require "test/support/helper/fixtures"
require "test/support/helper/formula"
require "test/support/helper/mktmpdir"
+
+require "test/support/helper/spec/shared_context/homebrew_cask" if OS.mac?
require "test/support/helper/spec/shared_context/integration_test"
TEST_DIRECTORIES = [
diff --git a/Library/Homebrew/test/support/helper/spec/shared_context/homebrew_cask.rb b/Library/Homebrew/test/support/helper/spec/shared_context/homebrew_cask.rb
new file mode 100644
index 000000000..c51d339a7
--- /dev/null
+++ b/Library/Homebrew/test/support/helper/spec/shared_context/homebrew_cask.rb
@@ -0,0 +1,45 @@
+$LOAD_PATH.push(HOMEBREW_LIBRARY_PATH.join("cask", "lib").to_s)
+
+require "hbc"
+
+require "test/support/helper/cask/fake_system_command"
+require "test/support/helper/cask/install_helper"
+require "test/support/helper/cask/never_sudo_system_command"
+
+HOMEBREW_CASK_DIRS = [
+ :appdir,
+ :caskroom,
+ :cache,
+ :prefpanedir,
+ :qlplugindir,
+ :servicedir,
+ :binarydir,
+].freeze
+
+RSpec.shared_context "Homebrew-Cask" do
+ around(:each) do |example|
+ begin
+ dirs = HOMEBREW_CASK_DIRS.map do |dir|
+ Pathname.new(TEST_TMPDIR).join("cask-#{dir}").tap do |path|
+ path.mkpath
+ Hbc.public_send("#{dir}=", path)
+ end
+ end
+
+ Hbc.default_tap = Tap.fetch("caskroom", "spec").tap do |tap|
+ FileUtils.mkdir_p tap.path.dirname
+ FileUtils.ln_sf TEST_FIXTURE_DIR.join("cask"), tap.path
+ end
+
+ example.run
+ ensure
+ FileUtils.rm_rf dirs
+ Hbc.default_tap.path.unlink
+ FileUtils.rm_rf Hbc.default_tap.path.parent
+ end
+ end
+end
+
+RSpec.configure do |config|
+ config.include_context "Homebrew-Cask", :cask
+end