diff options
| author | Markus Reiter | 2017-03-05 06:31:36 +0100 | 
|---|---|---|
| committer | Markus Reiter | 2017-03-05 23:08:14 +0100 | 
| commit | 9fc6c7b2be300ff35dc52d80f4dc38d36d52ddc2 (patch) | |
| tree | 43e99a683329471c1dc965dcc92daccb57df7e8d /Library/Homebrew/test | |
| parent | 67ec76d1492fbb03959a782a85c4fb985d6a5884 (diff) | |
| download | brew-9fc6c7b2be300ff35dc52d80f4dc38d36d52ddc2.tar.bz2 | |
Move Cask specs into `brew tests`.
Diffstat (limited to 'Library/Homebrew/test')
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  | 
