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:
Roland Walker 2014-06-13 12:24:34 -04:00
parent 077eecb731
commit 3bd2e2a3f5
6 changed files with 333 additions and 288 deletions

View File

@ -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

View File

@ -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

View File

@ -0,0 +1,2 @@
class Cask::Artifact::Uninstall < Cask::Artifact::UninstallBase
end

View File

@ -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

View File

@ -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

View File

@ -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