diff --git a/lib/cask/artifact.rb b/lib/cask/artifact.rb index efcd9ba7c2b9..bfb16b005f21 100644 --- a/lib/cask/artifact.rb +++ b/lib/cask/artifact.rb @@ -1,6 +1,7 @@ module Cask::Artifact; end require 'cask/artifact/base' +require 'cask/artifact/uninstall_base' require 'cask/artifact/symlinked' require 'cask/artifact/hardlinked' @@ -19,6 +20,7 @@ module Cask::Artifact; end require 'cask/artifact/caskroom_only' require 'cask/artifact/input_method' require 'cask/artifact/screen_saver' +require 'cask/artifact/uninstall' module Cask::Artifact @@ -42,6 +44,7 @@ def self.artifacts Cask::Artifact::Binary, Cask::Artifact::InputMethod, Cask::Artifact::ScreenSaver, + Cask::Artifact::Uninstall, Cask::Artifact::AfterBlock, ] end diff --git a/lib/cask/artifact/after_block.rb b/lib/cask/artifact/after_block.rb index 8fc73213bf5e..f2fdd2a5a064 100644 --- a/lib/cask/artifact/after_block.rb +++ b/lib/cask/artifact/after_block.rb @@ -4,11 +4,11 @@ def self.me?(cask) cask.artifacts[:after_uninstall].any? end - def install + def install_phase @cask.artifacts[:after_install].each { |block| @cask.instance_eval &block } end - def uninstall + def uninstall_phase @cask.artifacts[:after_uninstall].each { |block| @cask.instance_eval &block } end end diff --git a/lib/cask/artifact/before_block.rb b/lib/cask/artifact/before_block.rb index a9eaa8bc336f..4915b56ae74a 100644 --- a/lib/cask/artifact/before_block.rb +++ b/lib/cask/artifact/before_block.rb @@ -4,11 +4,11 @@ def self.me?(cask) cask.artifacts[:before_uninstall].any? end - def install + def install_phase @cask.artifacts[:before_install].each { |block| @cask.instance_eval &block } end - def uninstall + def uninstall_phase @cask.artifacts[:before_uninstall].each { |block| @cask.instance_eval &block } end end diff --git a/lib/cask/artifact/binary.rb b/lib/cask/artifact/binary.rb index 6743ddf48d49..f09b2de7b586 100644 --- a/lib/cask/artifact/binary.rb +++ b/lib/cask/artifact/binary.rb @@ -1,5 +1,5 @@ class Cask::Artifact::Binary < Cask::Artifact::Symlinked - def install + def install_phase super unless Cask.no_binaries end end diff --git a/lib/cask/artifact/nested_container.rb b/lib/cask/artifact/nested_container.rb index 51f65f5632cf..338776e55261 100644 --- a/lib/cask/artifact/nested_container.rb +++ b/lib/cask/artifact/nested_container.rb @@ -1,9 +1,9 @@ class Cask::Artifact::NestedContainer < Cask::Artifact::Base - def install + def install_phase @cask.artifacts[:nested_container].each { |container| extract(container) } end - def uninstall + def uninstall_phase # no need to take action; we will get removed by rmtree of parent end diff --git a/lib/cask/artifact/pkg.rb b/lib/cask/artifact/pkg.rb index daaa3b025842..f6bd0c303a27 100644 --- a/lib/cask/artifact/pkg.rb +++ b/lib/cask/artifact/pkg.rb @@ -1,40 +1,8 @@ class Cask::Artifact::Pkg < Cask::Artifact::Base - # this class actually covers two keys, :install and :uninstall def self.artifact_dsl_key :install end - def self.read_script_arguments(uninstall_options, key) - script_arguments = uninstall_options[key] - - # backwards-compatible string value - if script_arguments.kind_of?(String) - script_arguments = { :executable => script_arguments } - end - - # key sanity - permitted_keys = [:args, :input, :executable, :must_succeed] - unknown_keys = script_arguments.keys - permitted_keys - unless unknown_keys.empty? - opoo %Q{Unknown arguments to uninstall :#{key} -- :#{unknown_keys.join(", :")} (ignored). Running "brew update && brew upgrade brew-cask && brew cleanup && brew cask cleanup" will likely fix it.} - end - script_arguments.reject! {|k,v| ! permitted_keys.include?(k)} - - # extract executable - if script_arguments.key?(:executable) - executable = script_arguments.delete(:executable) - else - executable = nil - end - - unless script_arguments.key?(:must_succeed) - script_arguments[:must_succeed] = true - end - - script_arguments.merge!(:sudo => true, :print => true) - return executable, script_arguments - end - def load_pkg_description(pkg_description) @pkg_relative_path = pkg_description.shift @pkg_install_opts = pkg_description.shift @@ -58,12 +26,12 @@ def pkg_relative_path @pkg_relative_path end - def install + def install_phase @cask.artifacts[:install].each { |pkg_description| run_installer(pkg_description) } end - def uninstall - manually_uninstall(@cask.artifacts[:uninstall]) + def uninstall_phase + # Do nothing. Must be handled explicitly by a separate :uninstall stanza. end def run_installer(pkg_description) @@ -81,113 +49,4 @@ def run_installer(pkg_description) args << '-allowUntrusted' if pkg_install_opts :allow_untrusted @command.run!('/usr/sbin/installer', {:sudo => true, :args => args, :print => true}) end - - def manually_uninstall(uninstall_set) - ohai "Running uninstall process for #{@cask}; your password may be necessary" - - uninstall_set.each do |uninstall_options| - unknown_keys = uninstall_options.keys - [:early_script, :launchctl, :quit, :signal, :kext, :script, :pkgutil, :files] - unless unknown_keys.empty? - opoo %Q{Unknown arguments to uninstall: #{unknown_keys.join(", ")}. Running "brew update && brew upgrade brew-cask && brew cleanup && brew cask cleanup" will likely fix it.} - end - end - - # Preserve prior functionality of script which runs first. Should rarely be needed. - # :early_script should not delete files, better defer that to :script. - # If Cask writers never need :early_script it may be removed in the future. - uninstall_set.select{ |h| h.key?(:early_script) }.each do |uninstall_options| - executable, script_arguments = self.class.read_script_arguments(uninstall_options, :early_script) - ohai "Running uninstall script #{executable}" - raise CaskInvalidError.new(@cask, 'uninstall :early_script without :executable') if executable.nil? - @command.run(@cask.destination_path.join(executable), script_arguments) - sleep 1 - end - - # :launchctl must come before :quit/:signal for cases where app would instantly re-launch - uninstall_set.select{ |h| h.key?(:launchctl) }.each do |uninstall_options| - Array(uninstall_options[:launchctl]).each do |service| - ohai "Removing launchctl service #{service}" - [false, true].each do |with_sudo| - xml_status = @command.run('/bin/launchctl', :args => ['list', '-x', service], :sudo => with_sudo) - if %r{^<\?xml}.match(xml_status) - @command.run('/bin/launchctl', :args => ['unload', '-w', '--', service], :sudo => with_sudo) - sleep 1 - @command.run!('/bin/launchctl', :args => ['remove', service], :sudo => with_sudo) - sleep 1 - end - end - end - end - - # :quit/:signal must come before :kext so the kext will not be in use by a running process - uninstall_set.select{ |h| h.key?(:quit) }.each do |uninstall_options| - Array(uninstall_options[:quit]).each do |id| - ohai "Quitting application ID #{id}" - num_running = @command.run!('/usr/bin/osascript', :args => ['-e', %Q{tell application "System Events" to count processes whose bundle identifier is "#{id}"}], :sudo => true).to_i - if num_running > 0 - @command.run!('/usr/bin/osascript', :args => ['-e', %Q{tell application id "#{id}" to quit}], :sudo => true) - sleep 3 - end - end - end - - # :signal should come after :quit so it can be used as a backup when :quit fails - uninstall_set.select{ |h| h.key?(:signal) }.each do |uninstall_options| - Array(uninstall_options[:signal]).flatten.each_slice(2) do |pair| - raise CaskInvalidError.new(@cask, 'Each uninstall :signal must have 2 elements.') unless pair.length == 2 - signal, id = pair - ohai "Signalling '#{signal}' to application ID '#{id}'" - pid_string = @command.run!('/usr/bin/osascript', :args => ['-e', %Q{tell application "System Events" to get the unix id of every process whose bundle identifier is "#{id}"}], :sudo => true) - if pid_string.match(%r{\A\d+(?:\s*,\s*\d+)*\Z}) # sanity check - pids = pid_string.split(%r{\s*,\s*}).map(&:strip).map(&:to_i) - if pids.length > 0 - # Note that unlike :quit, signals are sent from the - # current user (not upgraded to the superuser). This is a - # todo item for the future, but there should be some - # additional thought/safety checks about that, as a - # misapplied "kill" by root could bring down the system. - # The fact that we learned the pid from AppleScript is - # already some degree of protection, though indirect. - Process.kill(signal, *pids) - sleep 3 - end - end - end - end - - # :kext should be unloaded before attempting to delete the relevant file - uninstall_set.select{ |h| h.key?(:kext) }.each do |uninstall_options| - Array(uninstall_options[:kext]).each do |kext| - ohai "Unloading kernel extension #{kext}" - is_loaded = @command.run!('/usr/sbin/kextstat', :args => ['-l', '-b', kext], :sudo => true) - if is_loaded.length > 1 - @command.run!('/sbin/kextunload', :args => ['-b', '--', kext], :sudo => true) - sleep 1 - end - end - end - - # :script must come before :pkgutil or :files so that the script file is not already deleted - uninstall_set.select{ |h| h.key?(:script) }.each do |uninstall_options| - executable, script_arguments = self.class.read_script_arguments(uninstall_options, :script) - raise CaskInvalidError.new(@cask, 'uninstall :script without :executable.') if executable.nil? - @command.run(@cask.destination_path.join(executable), script_arguments) - sleep 1 - end - - uninstall_set.select{ |h| h.key?(:pkgutil) }.each do |uninstall_options| - ohai "Removing files from pkgutil Bill-of-Materials" - Array(uninstall_options[:pkgutil]).each do |regexp| - pkgs = Cask::Pkg.all_matching(regexp, @command) - pkgs.each(&:uninstall) - end - end - - uninstall_set.select{ |h| h.key?(:files) }.each do |uninstall_options| - Array(uninstall_options[:files]).flatten.each_slice(500) do |file_slice| - ohai "Removing files: #{file_slice.utf8_inspect}" - @command.run!('/bin/rm', :args => file_slice.unshift('-rf', '--'), :sudo => true) - end - end - end end diff --git a/lib/cask/artifact/symlinked.rb b/lib/cask/artifact/symlinked.rb index 8f7d7a3d4e6d..41142f2c5bae 100644 --- a/lib/cask/artifact/symlinked.rb +++ b/lib/cask/artifact/symlinked.rb @@ -82,12 +82,12 @@ def unlink(artifact_spec) end end - def install + def install_phase # the sort is for predictability between Ruby versions @cask.artifacts[self.class.artifact_dsl_key].sort.each { |artifact| link(artifact) } end - def uninstall + def uninstall_phase # the sort is for predictability between Ruby versions @cask.artifacts[self.class.artifact_dsl_key].sort.each { |artifact| unlink(artifact) } end diff --git a/lib/cask/artifact/uninstall.rb b/lib/cask/artifact/uninstall.rb new file mode 100644 index 000000000000..09fbf0176e2a --- /dev/null +++ b/lib/cask/artifact/uninstall.rb @@ -0,0 +1,2 @@ +class Cask::Artifact::Uninstall < Cask::Artifact::UninstallBase +end diff --git a/lib/cask/artifact/uninstall_base.rb b/lib/cask/artifact/uninstall_base.rb new file mode 100644 index 000000000000..4af12d23832c --- /dev/null +++ b/lib/cask/artifact/uninstall_base.rb @@ -0,0 +1,154 @@ +class Cask::Artifact::UninstallBase < Cask::Artifact::Base + + # todo: these methods were consolidated here from separate + # sources and, should be refactored for consistency + + def install_phase + odebug "Nothing to do. The uninstall artifact has no install phase." + end + + def uninstall_phase + dispatch_uninstall_directives(self.class.artifact_dsl_key) + end + + def self.read_script_arguments(uninstall_options, key) + script_arguments = uninstall_options[key] + + # backwards-compatible string value + if script_arguments.kind_of?(String) + script_arguments = { :executable => script_arguments } + end + + # key sanity + permitted_keys = [:args, :input, :executable, :must_succeed] + unknown_keys = script_arguments.keys - permitted_keys + unless unknown_keys.empty? + opoo %Q{Unknown arguments to #{stanza} #{key.inspect} -- #{unknown_keys.inspect} (ignored). Running "brew update && brew upgrade brew-cask && brew cleanup && brew cask cleanup" will likely fix it.} + end + script_arguments.reject! {|k,v| ! permitted_keys.include?(k)} + + # extract executable + if script_arguments.key?(:executable) + executable = script_arguments.delete(:executable) + else + executable = nil + end + + unless script_arguments.key?(:must_succeed) + script_arguments[:must_succeed] = true + end + + script_arguments.merge!(:sudo => true, :print => true) + return executable, script_arguments + end + + def dispatch_uninstall_directives(stanza) + directives_set = @cask.artifacts[stanza] + ohai "Running #{stanza} process for #{@cask}; your password may be necessary" + + directives_set.each do |directives| + unknown_keys = directives.keys - [:early_script, :launchctl, :quit, :signal, :kext, :script, :pkgutil, :files] + unless unknown_keys.empty? + opoo %Q{Unknown arguments to #{stanza} -- #{unknown_keys.inspect}. Running "brew update && brew upgrade brew-cask && brew cleanup && brew cask cleanup" will likely fix it.} + end + end + + # Preserve prior functionality of script which runs first. Should rarely be needed. + # :early_script should not delete files, better defer that to :script. + # If Cask writers never need :early_script it may be removed in the future. + directives_set.select{ |h| h.key?(:early_script) }.each do |directives| + executable, script_arguments = self.class.read_script_arguments(directives, :early_script) + ohai "Running uninstall script #{executable}" + raise CaskInvalidError.new(@cask, "#{stanza} :early_script without :executable") if executable.nil? + @command.run(@cask.destination_path.join(executable), script_arguments) + sleep 1 + end + + # :launchctl must come before :quit/:signal for cases where app would instantly re-launch + directives_set.select{ |h| h.key?(:launchctl) }.each do |directives| + Array(directives[:launchctl]).each do |service| + ohai "Removing launchctl service #{service}" + [false, true].each do |with_sudo| + xml_status = @command.run('/bin/launchctl', :args => ['list', '-x', service], :sudo => with_sudo) + if %r{^<\?xml}.match(xml_status) + @command.run('/bin/launchctl', :args => ['unload', '-w', '--', service], :sudo => with_sudo) + sleep 1 + @command.run!('/bin/launchctl', :args => ['remove', service], :sudo => with_sudo) + sleep 1 + end + end + end + end + + # :quit/:signal must come before :kext so the kext will not be in use by a running process + directives_set.select{ |h| h.key?(:quit) }.each do |directives| + Array(directives[:quit]).each do |id| + ohai "Quitting application ID #{id}" + num_running = @command.run!('/usr/bin/osascript', :args => ['-e', %Q{tell application "System Events" to count processes whose bundle identifier is "#{id}"}], :sudo => true).to_i + if num_running > 0 + @command.run!('/usr/bin/osascript', :args => ['-e', %Q{tell application id "#{id}" to quit}], :sudo => true) + sleep 3 + end + end + end + + # :signal should come after :quit so it can be used as a backup when :quit fails + directives_set.select{ |h| h.key?(:signal) }.each do |directives| + Array(directives[:signal]).flatten.each_slice(2) do |pair| + raise CaskInvalidError.new(@cask, "Each #{stanza} :signal must have 2 elements.") unless pair.length == 2 + signal, id = pair + ohai "Signalling '#{signal}' to application ID '#{id}'" + pid_string = @command.run!('/usr/bin/osascript', :args => ['-e', %Q{tell application "System Events" to get the unix id of every process whose bundle identifier is "#{id}"}], :sudo => true) + if pid_string.match(%r{\A\d+(?:\s*,\s*\d+)*\Z}) # sanity check + pids = pid_string.split(%r{\s*,\s*}).map(&:strip).map(&:to_i) + if pids.length > 0 + # Note that unlike :quit, signals are sent from the + # current user (not upgraded to the superuser). This is a + # todo item for the future, but there should be some + # additional thought/safety checks about that, as a + # misapplied "kill" by root could bring down the system. + # The fact that we learned the pid from AppleScript is + # already some degree of protection, though indirect. + Process.kill(signal, *pids) + sleep 3 + end + end + end + end + + # :kext should be unloaded before attempting to delete the relevant file + directives_set.select{ |h| h.key?(:kext) }.each do |directives| + Array(directives[:kext]).each do |kext| + ohai "Unloading kernel extension #{kext}" + is_loaded = @command.run!('/usr/sbin/kextstat', :args => ['-l', '-b', kext], :sudo => true) + if is_loaded.length > 1 + @command.run!('/sbin/kextunload', :args => ['-b', '--', kext], :sudo => true) + sleep 1 + end + end + end + + # :script must come before :pkgutil or :files so that the script file is not already deleted + directives_set.select{ |h| h.key?(:script) }.each do |directives| + executable, script_arguments = self.class.read_script_arguments(directives, :script) + raise CaskInvalidError.new(@cask, "#{stanza} :script without :executable.") if executable.nil? + @command.run(@cask.destination_path.join(executable), script_arguments) + sleep 1 + end + + directives_set.select{ |h| h.key?(:pkgutil) }.each do |directives| + ohai "Removing files from pkgutil Bill-of-Materials" + Array(directives[:pkgutil]).each do |regexp| + pkgs = Cask::Pkg.all_matching(regexp, @command) + pkgs.each(&:uninstall) + end + end + + directives_set.select{ |h| h.key?(:files) }.each do |directives| + Array(directives[:files]).flatten.each_slice(500) do |file_slice| + ohai "Removing files: #{file_slice.utf8_inspect}" + @command.run!('/bin/rm', :args => file_slice.unshift('-rf', '--'), :sudo => true) + end + end + end +end diff --git a/lib/cask/installer.rb b/lib/cask/installer.rb index 27469e4db295..cdc7e33bdb93 100644 --- a/lib/cask/installer.rb +++ b/lib/cask/installer.rb @@ -81,7 +81,7 @@ def install_artifacts odebug "#{artifacts.length} artifact/s defined", artifacts artifacts.each do |artifact| odebug "Installing artifact of class #{artifact}" - artifact.new(@cask, @command).install + artifact.new(@cask, @command).install_phase end end @@ -124,7 +124,7 @@ def uninstall_artifacts odebug "#{artifacts.length} artifact/s defined", artifacts artifacts.each do |artifact| odebug "Un-installing artifact of class #{artifact}" - artifact.new(@cask, @command).uninstall + artifact.new(@cask, @command).uninstall_phase end end diff --git a/test/cask/artifact/after_block_test.rb b/test/cask/artifact/after_block_test.rb index f4523fc314c8..9fa796612aa6 100644 --- a/test/cask/artifact/after_block_test.rb +++ b/test/cask/artifact/after_block_test.rb @@ -1,7 +1,7 @@ require 'test_helper' describe Cask::Artifact::AfterBlock do - describe 'install' do + describe 'install_phase' do it 'calls the specified block after installing, passing the cask' do called = false yielded_arg = nil @@ -15,14 +15,14 @@ end cask = CaskWithAfterInstall.new - Cask::Artifact::AfterBlock.new(cask).install + Cask::Artifact::AfterBlock.new(cask).install_phase called.must_equal true yielded_arg.must_equal cask end end - describe 'uninstall' do + describe 'uninstall_phase' do it 'calls the specified block after uninstalling, passing the cask' do called = false yielded_arg = nil @@ -36,7 +36,7 @@ end cask = CaskWithAfterUninstall.new - Cask::Artifact::AfterBlock.new(cask).uninstall + Cask::Artifact::AfterBlock.new(cask).uninstall_phase called.must_equal true yielded_arg.must_equal cask diff --git a/test/cask/artifact/alt_target_test.rb b/test/cask/artifact/alt_target_test.rb index af66fe379345..0a85c74f8b19 100644 --- a/test/cask/artifact/alt_target_test.rb +++ b/test/cask/artifact/alt_target_test.rb @@ -12,7 +12,7 @@ cask = local_alt_caffeine shutup do - Cask::Artifact::App.new(cask).install + Cask::Artifact::App.new(cask).install_phase end TestHelper.valid_alias?(Cask.appdir/'AnotherName.app').must_equal true @@ -22,7 +22,7 @@ cask = local_alt_caffeine shutup do - Cask::Artifact::App.new(cask).install + Cask::Artifact::App.new(cask).install_phase end Cask::SystemCommand.run('/usr/bin/xattr', @@ -51,7 +51,7 @@ FileUtils.mv((subdir_cask.destination_path/'Caffeine.app'), appsubdir) shutup do - Cask::Artifact::App.new(subdir_cask).install + Cask::Artifact::App.new(subdir_cask).install_phase end TestHelper.valid_alias?(Cask.appdir/'AnotherName.app').must_equal true @@ -71,7 +71,7 @@ FileUtils.cp_r app_path, app_path.sub('Caffeine.app', 'CaffeineAgain.app') shutup do - Cask::Artifact::App.new(cask).install + Cask::Artifact::App.new(cask).install_phase end TestHelper.valid_alias?(Cask.appdir/'AnotherName.app').must_equal true @@ -84,7 +84,7 @@ (Cask.appdir/'AnotherName.app').mkpath TestHelper.must_output(self, lambda { - Cask::Artifact::App.new(cask).install + Cask::Artifact::App.new(cask).install_phase }, "==> It seems there is already an App at '#{Cask.appdir.join('AnotherName.app')}'; not linking.") (Cask.appdir/'AnotherName.app').wont_be :symlink? @@ -96,7 +96,7 @@ (Cask.appdir/'AnotherName.app').make_symlink('/tmp') TestHelper.must_output(self, lambda { - Cask::Artifact::App.new(cask).install + Cask::Artifact::App.new(cask).install_phase }, "==> Symlinking App 'Caffeine.app' to '#{Cask.appdir.join('AnotherName.app')}'") File.readlink(Cask.appdir/'AnotherName.app').wont_equal '/tmp' diff --git a/test/cask/artifact/app_test.rb b/test/cask/artifact/app_test.rb index 07d20d3f5ddc..94ccbf092098 100644 --- a/test/cask/artifact/app_test.rb +++ b/test/cask/artifact/app_test.rb @@ -7,12 +7,12 @@ end } - describe 'install' do + describe 'install_phase' do it "links the noted applications to the proper directory" do cask = local_caffeine shutup do - Cask::Artifact::App.new(cask).install + Cask::Artifact::App.new(cask).install_phase end TestHelper.valid_alias?(Cask.appdir/'Caffeine.app').must_equal true @@ -37,7 +37,7 @@ FileUtils.mv((subdir_cask.destination_path/'Caffeine.app'), appsubdir) shutup do - Cask::Artifact::App.new(subdir_cask).install + Cask::Artifact::App.new(subdir_cask).install_phase end TestHelper.valid_alias?(Cask.appdir/'Caffeine.app').must_equal true @@ -57,7 +57,7 @@ FileUtils.cp_r app_path, app_path.sub('Caffeine.app', 'CaffeineAgain.app') shutup do - Cask::Artifact::App.new(cask).install + Cask::Artifact::App.new(cask).install_phase end TestHelper.valid_alias?(Cask.appdir/'Caffeine.app').must_equal true @@ -70,7 +70,7 @@ (Cask.appdir/'Caffeine.app').mkpath TestHelper.must_output(self, lambda { - Cask::Artifact::App.new(cask).install + Cask::Artifact::App.new(cask).install_phase }, "==> It seems there is already an App at '#{Cask.appdir.join('Caffeine.app')}'; not linking.") (Cask.appdir/'Caffeine.app').wont_be :symlink? @@ -82,7 +82,7 @@ (Cask.appdir/'Caffeine.app').make_symlink('/tmp') TestHelper.must_output(self, lambda { - Cask::Artifact::App.new(cask).install + Cask::Artifact::App.new(cask).install_phase }, "==> Symlinking App 'Caffeine.app' to '#{Cask.appdir.join('Caffeine.app')}'") File.readlink(Cask.appdir/'Caffeine.app').wont_equal '/tmp' diff --git a/test/cask/artifact/before_block_test.rb b/test/cask/artifact/before_block_test.rb index e80bec077141..a7c3aa7f2578 100644 --- a/test/cask/artifact/before_block_test.rb +++ b/test/cask/artifact/before_block_test.rb @@ -1,7 +1,7 @@ require 'test_helper' describe Cask::Artifact::BeforeBlock do - describe 'install' do + describe 'install_phase' do it 'calls the specified block before installing' do called = false yielded_arg = nil @@ -15,14 +15,14 @@ end cask = CaskWithBeforeInstall.new - Cask::Artifact::BeforeBlock.new(cask).install + Cask::Artifact::BeforeBlock.new(cask).install_phase called.must_equal true yielded_arg.must_equal cask end end - describe 'uninstall' do + describe 'uninstall_phase' do it 'calls the specified block before uninstalling, passing the cask' do called = false yielded_arg = nil @@ -36,7 +36,7 @@ end cask = CaskWithBeforeUninstall.new - Cask::Artifact::BeforeBlock.new(cask).uninstall + Cask::Artifact::BeforeBlock.new(cask).uninstall_phase called.must_equal true yielded_arg.must_equal cask diff --git a/test/cask/artifact/binary_test.rb b/test/cask/artifact/binary_test.rb index b50467c09e26..9934b471966c 100644 --- a/test/cask/artifact/binary_test.rb +++ b/test/cask/artifact/binary_test.rb @@ -12,7 +12,7 @@ it "links the binary to the proper directory" do shutup do - Cask::Artifact::Binary.new(cask).install + Cask::Artifact::Binary.new(cask).install_phase end TestHelper.valid_alias?(expected_path).must_equal true @@ -22,7 +22,7 @@ FileUtils.touch expected_path shutup do - Cask::Artifact::Binary.new(cask).install + Cask::Artifact::Binary.new(cask).install_phase end expected_path.wont_be :symlink? @@ -32,7 +32,7 @@ expected_path.make_symlink('/tmp') shutup do - Cask::Artifact::Binary.new(cask).install + Cask::Artifact::Binary.new(cask).install_phase end File.readlink(expected_path).wont_equal '/tmp' @@ -42,7 +42,7 @@ Cask.no_binaries = true shutup do - Cask::Artifact::Binary.new(cask).install + Cask::Artifact::Binary.new(cask).install_phase end expected_path.exist?.must_equal false diff --git a/test/cask/artifact/nested_container_test.rb b/test/cask/artifact/nested_container_test.rb index 5f0f14aafe3c..7c78aaa7d86f 100644 --- a/test/cask/artifact/nested_container_test.rb +++ b/test/cask/artifact/nested_container_test.rb @@ -8,7 +8,7 @@ end shutup do - Cask::Artifact::NestedContainer.new(cask).install + Cask::Artifact::NestedContainer.new(cask).install_phase end cask.destination_path.join('MyNestedApp.app').must_be :directory? diff --git a/test/cask/artifact/pkg_test.rb b/test/cask/artifact/pkg_test.rb index 31eefbfe46eb..74a264fcf36f 100644 --- a/test/cask/artifact/pkg_test.rb +++ b/test/cask/artifact/pkg_test.rb @@ -8,167 +8,23 @@ end } - describe 'install' do + describe 'install_phase' do it 'runs the system installer on the specified pkgs' do pkg = Cask::Artifact::Pkg.new(@cask, Cask::FakeSystemCommand) Cask::FakeSystemCommand.expects_command(['/usr/bin/sudo', '-E', '--', '/usr/sbin/installer', '-pkg', @cask.destination_path/'MyFancyPkg'/'Fancy.pkg', '-target', '/']) shutup do - pkg.install + pkg.install_phase end end end - describe 'uninstall' do - # todo: uninstall tests for :signal (implementation does not use SystemComment) - it 'runs the specified uninstaller for the cask' do + describe 'uninstall_phase' do + it 'does nothing, because the uninstall_phase method is a no-op' do pkg = Cask::Artifact::Pkg.new(@cask, Cask::FakeSystemCommand) - - Cask::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') - Cask::FakeSystemCommand.stubs_command(['/usr/bin/sudo', '-E', '--', '/usr/bin/osascript', '-e', 'tell application id "my.fancy.package.app" to quit']) - - Cask::FakeSystemCommand.expects_command(['/usr/bin/sudo', '-E', '--', @cask.destination_path/'MyFancyPkg'/'FancyUninstaller.tool', '--please']) - - shutup do - pkg.uninstall - end - end - - it 'can uninstall using pkgutil, launchctl, and file lists' do - cask = Cask.load('with-pkgutil-uninstall') - pkg = Cask::Artifact::Pkg.new(cask, Cask::FakeSystemCommand) - - Cask::FakeSystemCommand.stubs_command( - ['/usr/sbin/pkgutil', '--pkgs=my.fancy.package.*'], - [ - 'my.fancy.package.main', - 'my.fancy.package.agent', - ].join("\n") - ) - - Cask::FakeSystemCommand.stubs_command( - ['/usr/sbin/pkgutil', '--only-files', '--files', 'my.fancy.package.main'], - [ - 'fancy/bin/fancy.exe', - 'fancy/var/fancy.data', - ].join("\n") - ) - Cask::FakeSystemCommand.stubs_command( - ['/usr/sbin/pkgutil', '--only-dirs', '--files', 'my.fancy.package.main'], - [ - 'fancy', - 'fancy/bin', - 'fancy/var', - ].join("\n") - ) - Cask::FakeSystemCommand.stubs_command( - ['/usr/sbin/pkgutil', '--pkg-info-plist', 'my.fancy.package.main'], - <<-PLIST - - - - - install-location - tmp - volume - / - - - PLIST - ) - - Cask::FakeSystemCommand.stubs_command( - ['/bin/launchctl', 'list', '-x', 'my.fancy.package.service'], - "launchctl list returned unknown response\n" - ) - Cask::FakeSystemCommand.stubs_command( - ['/usr/bin/sudo', '-E', '--', '/bin/launchctl', 'list', '-x', 'my.fancy.package.service'], - <<-"PLIST" - - - - -\tLabel -\tmy.fancy.package.service -\tLastExitStatus -\t0 -\tLimitLoadToSessionType -\tSystem -\tOnDemand -\t -\tProgramArguments -\t -\t\targument -\t -\tTimeOut -\t30 - - - PLIST - ) - - Cask::FakeSystemCommand.expects_command(['/usr/bin/sudo', '-E', '--', '/bin/launchctl', 'remove', 'my.fancy.package.service']) - Cask::FakeSystemCommand.expects_command(['/usr/bin/sudo', '-E', '--', '/bin/launchctl', 'unload', '-w', '--', 'my.fancy.package.service']) - - Cask::FakeSystemCommand.stubs_command(['/usr/bin/sudo', '-E', '--', '/usr/sbin/kextstat', '-l', '-b', 'my.fancy.package.kernelextension'], 'loaded') - Cask::FakeSystemCommand.expects_command(['/usr/bin/sudo', '-E', '--', '/sbin/kextunload', '-b', '--', 'my.fancy.package.kernelextension']) - Cask::FakeSystemCommand.stubs_command(['/usr/bin/sudo', '-E', '--', '/usr/sbin/pkgutil', '--forget', 'my.fancy.package.main']) - - Cask::FakeSystemCommand.stubs_command( - ['/usr/sbin/pkgutil', '--only-files', '--files', 'my.fancy.package.agent'], - [ - 'fancy/agent/fancy-agent.exe', - 'fancy/agent/fancy-agent.pid', - 'fancy/agent/fancy-agent.log', - ].join("\n") - ) - Cask::FakeSystemCommand.stubs_command( - ['/usr/sbin/pkgutil', '--only-dirs', '--files', 'my.fancy.package.agent'], - [ - 'fancy', - 'fancy/agent', - ].join("\n") - ) - Cask::FakeSystemCommand.stubs_command( - ['/usr/sbin/pkgutil', '--pkg-info-plist', 'my.fancy.package.agent'], - <<-PLIST - - - - - install-location - tmp - volume - / - - - PLIST - ) - - %w[ - /tmp/fancy - /tmp/fancy/agent - /tmp/fancy/bin - /tmp/fancy/var - ].each do |dir| - Cask::FakeSystemCommand.stubs_command(['/usr/bin/sudo', '-E', '--', '/bin/chmod', '--', '777', '#{dir}']) - end - - Cask::FakeSystemCommand.stubs_command(['/usr/bin/sudo', '-E', '--', '/usr/sbin/pkgutil', '--forget', 'my.fancy.package.agent']) - - Cask::FakeSystemCommand.stubs_command(['/usr/bin/sudo', '-E', '--', '/bin/rm', '-f', '--', - Pathname.new('/tmp/fancy/bin/fancy.exe'), - Pathname.new('/tmp/fancy/var/fancy.data')]) - Cask::FakeSystemCommand.stubs_command(['/usr/bin/sudo', '-E', '--', '/bin/rm', '-f', '--', - Pathname.new('/tmp/fancy/agent/fancy-agent.exe'), - Pathname.new('/tmp/fancy/agent/fancy-agent.pid'), - Pathname.new('/tmp/fancy/agent/fancy-agent.log')]) - - # No assertions after call since all assertions are implicit from the interactions setup above. - # TODO: verify rmdir commands (requires setting up actual file tree or faking out .exists? shutup do - pkg.uninstall + pkg.uninstall_phase end end end diff --git a/test/cask/artifact/two_links_correct_test.rb b/test/cask/artifact/two_links_correct_test.rb index e637306402fd..cc22cce4a121 100644 --- a/test/cask/artifact/two_links_correct_test.rb +++ b/test/cask/artifact/two_links_correct_test.rb @@ -12,7 +12,7 @@ cask = local_two_links_caffeine shutup do - Cask::Artifact::App.new(cask).install + Cask::Artifact::App.new(cask).install_phase end TestHelper.valid_alias?(Cask.appdir/'Caffeine.app').must_equal true @@ -39,7 +39,7 @@ FileUtils.mv((subdir_cask.destination_path/'Caffeine.app'), appsubdir) shutup do - Cask::Artifact::App.new(subdir_cask).install + Cask::Artifact::App.new(subdir_cask).install_phase end TestHelper.valid_alias?(Cask.appdir/'Caffeine.app').must_equal true @@ -61,7 +61,7 @@ # FileUtils.cp_r app_path, app_path.sub('Caffeine.app', 'CaffeineAgain.app') # # shutup do - # Cask::Artifact::App.new(cask).install + # Cask::Artifact::App.new(cask).install_phase # end # # TestHelper.valid_alias?(Cask.appdir/'AnotherName.app').must_equal true @@ -74,7 +74,7 @@ (Cask.appdir/'Caffeine.app').mkpath TestHelper.must_output(self, lambda { - Cask::Artifact::App.new(cask).install + Cask::Artifact::App.new(cask).install_phase }, <<-MESSAGE.undent.chomp) ==> It seems there is already an App at '#{Cask.appdir.join('Caffeine.app')}'; not linking. ==> Symlinking App 'Caffeine.app' to '#{Cask.appdir.join('AnotherName.app')}' @@ -89,7 +89,7 @@ (Cask.appdir/'AnotherName.app').mkpath TestHelper.must_output(self, lambda { - Cask::Artifact::App.new(cask).install + Cask::Artifact::App.new(cask).install_phase }, <<-MESSAGE.undent.chomp) ==> Symlinking App 'Caffeine.app' to '#{Cask.appdir.join('Caffeine.app')}' ==> It seems there is already an App at '#{Cask.appdir.join('AnotherName.app')}'; not linking. @@ -104,7 +104,7 @@ (Cask.appdir/'Caffeine.app').make_symlink('/tmp') TestHelper.must_output(self, lambda { - Cask::Artifact::App.new(cask).install + Cask::Artifact::App.new(cask).install_phase }, <<-MESSAGE.undent.chomp) ==> Symlinking App 'Caffeine.app' to '#{Cask.appdir.join('Caffeine.app')}' ==> Symlinking App 'Caffeine.app' to '#{Cask.appdir.join('AnotherName.app')}' @@ -119,7 +119,7 @@ (Cask.appdir/'AnotherName.app').make_symlink('/tmp') TestHelper.must_output(self, lambda { - Cask::Artifact::App.new(cask).install + Cask::Artifact::App.new(cask).install_phase }, <<-MESSAGE.undent.chomp) ==> Symlinking App 'Caffeine.app' to '#{Cask.appdir.join('Caffeine.app')}' ==> Symlinking App 'Caffeine.app' to '#{Cask.appdir.join('AnotherName.app')}' diff --git a/test/cask/artifact/uninstall_test.rb b/test/cask/artifact/uninstall_test.rb new file mode 100644 index 000000000000..ed07a4d5045d --- /dev/null +++ b/test/cask/artifact/uninstall_test.rb @@ -0,0 +1,172 @@ +require 'test_helper' + +describe Cask::Artifact::Uninstall do + before { + @cask = Cask.load('with-installable') + shutup do + TestHelper.install_without_artifacts(@cask) + end + } + + describe 'install_phase' do + it 'does nothing, because the install_phase method is a no-op' do + uninstall_artifact = Cask::Artifact::Uninstall.new(@cask, Cask::FakeSystemCommand) + shutup do + uninstall_artifact.install_phase + end + end + end + + describe 'uninstall_phase' do + # todo: uninstall tests for :signal (implementation does not use SystemComment) + it 'runs the specified uninstaller for the cask' do + uninstall_artifact = Cask::Artifact::Uninstall.new(@cask, Cask::FakeSystemCommand) + + Cask::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') + Cask::FakeSystemCommand.stubs_command(['/usr/bin/sudo', '-E', '--', '/usr/bin/osascript', '-e', 'tell application id "my.fancy.package.app" to quit']) + + Cask::FakeSystemCommand.expects_command(['/usr/bin/sudo', '-E', '--', @cask.destination_path/'MyFancyPkg'/'FancyUninstaller.tool', '--please']) + + shutup do + uninstall_artifact.uninstall_phase + end + end + + it 'can uninstall using pkgutil, launchctl, and file lists' do + cask = Cask.load('with-pkgutil-uninstall') + uninstall_artifact = Cask::Artifact::Uninstall.new(cask, Cask::FakeSystemCommand) + + Cask::FakeSystemCommand.stubs_command( + ['/usr/sbin/pkgutil', '--pkgs=my.fancy.package.*'], + [ + 'my.fancy.package.main', + 'my.fancy.package.agent', + ].join("\n") + ) + + Cask::FakeSystemCommand.stubs_command( + ['/usr/sbin/pkgutil', '--only-files', '--files', 'my.fancy.package.main'], + [ + 'fancy/bin/fancy.exe', + 'fancy/var/fancy.data', + ].join("\n") + ) + Cask::FakeSystemCommand.stubs_command( + ['/usr/sbin/pkgutil', '--only-dirs', '--files', 'my.fancy.package.main'], + [ + 'fancy', + 'fancy/bin', + 'fancy/var', + ].join("\n") + ) + Cask::FakeSystemCommand.stubs_command( + ['/usr/sbin/pkgutil', '--pkg-info-plist', 'my.fancy.package.main'], + <<-PLIST + + + + + install-location + tmp + volume + / + + + PLIST + ) + + Cask::FakeSystemCommand.stubs_command( + ['/bin/launchctl', 'list', '-x', 'my.fancy.package.service'], + "launchctl list returned unknown response\n" + ) + Cask::FakeSystemCommand.stubs_command( + ['/usr/bin/sudo', '-E', '--', '/bin/launchctl', 'list', '-x', 'my.fancy.package.service'], + <<-"PLIST" + + + + +\tLabel +\tmy.fancy.package.service +\tLastExitStatus +\t0 +\tLimitLoadToSessionType +\tSystem +\tOnDemand +\t +\tProgramArguments +\t +\t\targument +\t +\tTimeOut +\t30 + + + PLIST + ) + + Cask::FakeSystemCommand.expects_command(['/usr/bin/sudo', '-E', '--', '/bin/launchctl', 'remove', 'my.fancy.package.service']) + Cask::FakeSystemCommand.expects_command(['/usr/bin/sudo', '-E', '--', '/bin/launchctl', 'unload', '-w', '--', 'my.fancy.package.service']) + + Cask::FakeSystemCommand.stubs_command(['/usr/bin/sudo', '-E', '--', '/usr/sbin/kextstat', '-l', '-b', 'my.fancy.package.kernelextension'], 'loaded') + Cask::FakeSystemCommand.expects_command(['/usr/bin/sudo', '-E', '--', '/sbin/kextunload', '-b', '--', 'my.fancy.package.kernelextension']) + Cask::FakeSystemCommand.stubs_command(['/usr/bin/sudo', '-E', '--', '/usr/sbin/pkgutil', '--forget', 'my.fancy.package.main']) + + Cask::FakeSystemCommand.stubs_command( + ['/usr/sbin/pkgutil', '--only-files', '--files', 'my.fancy.package.agent'], + [ + 'fancy/agent/fancy-agent.exe', + 'fancy/agent/fancy-agent.pid', + 'fancy/agent/fancy-agent.log', + ].join("\n") + ) + Cask::FakeSystemCommand.stubs_command( + ['/usr/sbin/pkgutil', '--only-dirs', '--files', 'my.fancy.package.agent'], + [ + 'fancy', + 'fancy/agent', + ].join("\n") + ) + Cask::FakeSystemCommand.stubs_command( + ['/usr/sbin/pkgutil', '--pkg-info-plist', 'my.fancy.package.agent'], + <<-PLIST + + + + + install-location + tmp + volume + / + + + PLIST + ) + + %w[ + /tmp/fancy + /tmp/fancy/agent + /tmp/fancy/bin + /tmp/fancy/var + ].each do |dir| + Cask::FakeSystemCommand.stubs_command(['/usr/bin/sudo', '-E', '--', '/bin/chmod', '--', '777', '#{dir}']) + end + + Cask::FakeSystemCommand.stubs_command(['/usr/bin/sudo', '-E', '--', '/usr/sbin/pkgutil', '--forget', 'my.fancy.package.agent']) + + Cask::FakeSystemCommand.stubs_command(['/usr/bin/sudo', '-E', '--', '/bin/rm', '-f', '--', + Pathname.new('/tmp/fancy/bin/fancy.exe'), + Pathname.new('/tmp/fancy/var/fancy.data')]) + Cask::FakeSystemCommand.stubs_command(['/usr/bin/sudo', '-E', '--', '/bin/rm', '-f', '--', + Pathname.new('/tmp/fancy/agent/fancy-agent.exe'), + Pathname.new('/tmp/fancy/agent/fancy-agent.pid'), + Pathname.new('/tmp/fancy/agent/fancy-agent.log')]) + + # No assertions after call since all assertions are implicit from the interactions setup above. + # TODO: verify rmdir commands (requires setting up actual file tree or faking out .exists? + shutup do + uninstall_artifact.uninstall_phase + end + end + end +end