promote `:uninstall` to an independent artifact
- separated from class `Cask::Artifact::Pkg` (the `:uninstall` DSL key) - `:uninstall` can now be invoked for all Casks - `uninstall_test.rb` also had to be separated from `pkg_test.rb`
This commit is contained in:
parent
077eecb731
commit
3bd2e2a3f5
|
@ -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 @@ require 'cask/artifact/service'
|
|||
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 @@ module Cask::Artifact
|
|||
Cask::Artifact::Binary,
|
||||
Cask::Artifact::InputMethod,
|
||||
Cask::Artifact::ScreenSaver,
|
||||
Cask::Artifact::Uninstall,
|
||||
Cask::Artifact::AfterBlock,
|
||||
]
|
||||
end
|
||||
|
|
|
@ -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
|
||||
|
@ -63,7 +31,7 @@ class Cask::Artifact::Pkg < Cask::Artifact::Base
|
|||
end
|
||||
|
||||
def uninstall
|
||||
dispatch_uninstall_directives(:uninstall)
|
||||
# Do nothing. Must be handled explicitly by a separate :uninstall stanza.
|
||||
end
|
||||
|
||||
def run_installer(pkg_description)
|
||||
|
@ -81,114 +49,4 @@ class Cask::Artifact::Pkg < Cask::Artifact::Base
|
|||
args << '-allowUntrusted' if pkg_install_opts :allow_untrusted
|
||||
@command.run!('/usr/sbin/installer', {:sudo => true, :args => args, :print => true})
|
||||
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
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
class Cask::Artifact::Uninstall < Cask::Artifact::UninstallBase
|
||||
end
|
|
@ -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
|
||||
odebug "Nothing to do. The uninstall artifact has no install phase."
|
||||
end
|
||||
|
||||
def uninstall
|
||||
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
|
|
@ -21,152 +21,8 @@ describe Cask::Artifact::Pkg do
|
|||
end
|
||||
|
||||
describe 'uninstall' do
|
||||
# todo: uninstall tests for :signal (implementation does not use SystemComment)
|
||||
it 'runs the specified uninstaller for the cask' do
|
||||
it 'does nothing, because the uninstall 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
|
||||
<?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>
|
||||
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"
|
||||
<?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>
|
||||
\t<key>Label</key>
|
||||
\t<string>my.fancy.package.service</string>
|
||||
\t<key>LastExitStatus</key>
|
||||
\t<integer>0</integer>
|
||||
\t<key>LimitLoadToSessionType</key>
|
||||
\t<string>System</string>
|
||||
\t<key>OnDemand</key>
|
||||
\t<true/>
|
||||
\t<key>ProgramArguments</key>
|
||||
\t<array>
|
||||
\t\t<string>argument</string>
|
||||
\t</array>
|
||||
\t<key>TimeOut</key>
|
||||
\t<integer>30</integer>
|
||||
</dict>
|
||||
</plist>
|
||||
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
|
||||
<?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>
|
||||
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
|
||||
end
|
||||
|
|
|
@ -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' do
|
||||
it 'does nothing, because the install method is a no-op' do
|
||||
uninstall_artifact = Cask::Artifact::Uninstall.new(@cask, Cask::FakeSystemCommand)
|
||||
shutup do
|
||||
uninstall_artifact.install
|
||||
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
|
||||
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
|
||||
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
|
||||
<?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>
|
||||
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"
|
||||
<?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>
|
||||
\t<key>Label</key>
|
||||
\t<string>my.fancy.package.service</string>
|
||||
\t<key>LastExitStatus</key>
|
||||
\t<integer>0</integer>
|
||||
\t<key>LimitLoadToSessionType</key>
|
||||
\t<string>System</string>
|
||||
\t<key>OnDemand</key>
|
||||
\t<true/>
|
||||
\t<key>ProgramArguments</key>
|
||||
\t<array>
|
||||
\t\t<string>argument</string>
|
||||
\t</array>
|
||||
\t<key>TimeOut</key>
|
||||
\t<integer>30</integer>
|
||||
</dict>
|
||||
</plist>
|
||||
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
|
||||
<?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>
|
||||
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
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue