Refactor Cask uninstall and zap stanza.
This commit is contained in:
parent
9105acab6b
commit
2691eb6f65
@ -34,7 +34,6 @@ module Hbc
|
|||||||
# stanza may not be needed as an explicit argument
|
# stanza may not be needed as an explicit argument
|
||||||
description = stanza.to_s
|
description = stanza.to_s
|
||||||
if key
|
if key
|
||||||
arguments = arguments[key]
|
|
||||||
description.concat(" #{key.inspect}")
|
description.concat(" #{key.inspect}")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@ -6,11 +6,6 @@ require "hbc/artifact/base"
|
|||||||
module Hbc
|
module Hbc
|
||||||
module Artifact
|
module Artifact
|
||||||
class UninstallBase < Base
|
class UninstallBase < Base
|
||||||
# TODO: 500 is also hardcoded in cask/pkg.rb, but much of
|
|
||||||
# that logic is probably in the wrong location
|
|
||||||
|
|
||||||
PATH_ARG_SLICE_SIZE = 500
|
|
||||||
|
|
||||||
ORDERED_DIRECTIVES = [
|
ORDERED_DIRECTIVES = [
|
||||||
:early_script,
|
:early_script,
|
||||||
:launchctl,
|
:launchctl,
|
||||||
@ -25,47 +20,7 @@ module Hbc
|
|||||||
:rmdir,
|
:rmdir,
|
||||||
].freeze
|
].freeze
|
||||||
|
|
||||||
# TODO: these methods were consolidated here from separate
|
def dispatch_uninstall_directives
|
||||||
# sources and should be refactored for consistency
|
|
||||||
|
|
||||||
def self.expand_path_strings(path_strings)
|
|
||||||
path_strings.map do |path_string|
|
|
||||||
path_string.start_with?("~") ? Pathname.new(path_string).expand_path : Pathname.new(path_string)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.expand_glob(path_strings)
|
|
||||||
path_strings.flat_map(&Pathname.method(:glob))
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.remove_relative_path_strings(action, path_strings)
|
|
||||||
relative = path_strings.map do |path_string|
|
|
||||||
path_string if %r{/\.\.(?:/|\Z)}.match(path_string) || !%r{\A/}.match(path_string)
|
|
||||||
end.compact
|
|
||||||
relative.each do |path_string|
|
|
||||||
opoo "Skipping #{action} for relative path #{path_string}"
|
|
||||||
end
|
|
||||||
path_strings - relative
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.remove_undeletable_path_strings(action, path_strings)
|
|
||||||
undeletable = path_strings.map do |path_string|
|
|
||||||
path_string if MacOS.undeletable?(Pathname.new(path_string))
|
|
||||||
end.compact
|
|
||||||
undeletable.each do |path_string|
|
|
||||||
opoo "Skipping #{action} for undeletable path #{path_string}"
|
|
||||||
end
|
|
||||||
path_strings - undeletable
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.prepare_path_strings(action, path_strings, expand_tilde)
|
|
||||||
path_strings = expand_path_strings(path_strings) if expand_tilde
|
|
||||||
path_strings = remove_relative_path_strings(action, path_strings)
|
|
||||||
path_strings = expand_glob(path_strings)
|
|
||||||
remove_undeletable_path_strings(action, path_strings)
|
|
||||||
end
|
|
||||||
|
|
||||||
def dispatch_uninstall_directives(expand_tilde: true)
|
|
||||||
directives_set = @cask.artifacts[stanza]
|
directives_set = @cask.artifacts[stanza]
|
||||||
ohai "Running #{stanza} process for #{@cask}; your password may be necessary"
|
ohai "Running #{stanza} process for #{@cask}; your password may be necessary"
|
||||||
|
|
||||||
@ -75,9 +30,8 @@ module Hbc
|
|||||||
|
|
||||||
ORDERED_DIRECTIVES.each do |directive_sym|
|
ORDERED_DIRECTIVES.each do |directive_sym|
|
||||||
directives_set.select { |h| h.key?(directive_sym) }.each do |directives|
|
directives_set.select { |h| h.key?(directive_sym) }.each do |directives|
|
||||||
args = [directives]
|
args = directives[directive_sym]
|
||||||
args << expand_tilde if [:delete, :trash, :rmdir].include?(directive_sym)
|
send("uninstall_#{directive_sym}", *(args.is_a?(Hash) ? [args] : args))
|
||||||
send("uninstall_#{directive_sym}", *args)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -102,8 +56,8 @@ module Hbc
|
|||||||
end
|
end
|
||||||
|
|
||||||
# :launchctl must come before :quit/:signal for cases where app would instantly re-launch
|
# :launchctl must come before :quit/:signal for cases where app would instantly re-launch
|
||||||
def uninstall_launchctl(directives)
|
def uninstall_launchctl(*services)
|
||||||
Array(directives[:launchctl]).each do |service|
|
services.each do |service|
|
||||||
ohai "Removing launchctl service #{service}"
|
ohai "Removing launchctl service #{service}"
|
||||||
[false, true].each do |with_sudo|
|
[false, true].each do |with_sudo|
|
||||||
plist_status = @command.run("/bin/launchctl", args: ["list", service], sudo: with_sudo, print_stderr: false).stdout
|
plist_status = @command.run("/bin/launchctl", args: ["list", service], sudo: with_sudo, print_stderr: false).stdout
|
||||||
@ -127,45 +81,6 @@ module Hbc
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# :quit/:signal must come before :kext so the kext will not be in use by a running process
|
|
||||||
def uninstall_quit(directives)
|
|
||||||
Array(directives[:quit]).each do |id|
|
|
||||||
ohai "Quitting application ID #{id}"
|
|
||||||
next if running_processes(id).empty?
|
|
||||||
@command.run!("/usr/bin/osascript", args: ["-e", %Q(tell application id "#{id}" to quit)], sudo: true)
|
|
||||||
|
|
||||||
begin
|
|
||||||
Timeout.timeout(3) do
|
|
||||||
Kernel.loop do
|
|
||||||
break if running_processes(id).empty?
|
|
||||||
end
|
|
||||||
end
|
|
||||||
rescue Timeout::Error
|
|
||||||
next
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# :signal should come after :quit so it can be used as a backup when :quit fails
|
|
||||||
def uninstall_signal(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, bundle_id = pair
|
|
||||||
ohai "Signalling '#{signal}' to application ID '#{bundle_id}'"
|
|
||||||
pids = running_processes(bundle_id).map(&:first)
|
|
||||||
next unless pids.any?
|
|
||||||
# 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.
|
|
||||||
odebug "Unix ids are #{pids.inspect} for processes with bundle identifier #{bundle_id}"
|
|
||||||
Process.kill(signal, *pids)
|
|
||||||
sleep 3
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def running_processes(bundle_id)
|
def running_processes(bundle_id)
|
||||||
@command.run!("/bin/launchctl", args: ["list"]).stdout.lines
|
@command.run!("/bin/launchctl", args: ["list"]).stdout.lines
|
||||||
.map { |line| line.chomp.split("\t") }
|
.map { |line| line.chomp.split("\t") }
|
||||||
@ -176,8 +91,50 @@ module Hbc
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def uninstall_login_item(directives)
|
# :quit/:signal must come before :kext so the kext will not be in use by a running process
|
||||||
Array(directives[:login_item]).each do |name|
|
def uninstall_quit(*bundle_ids)
|
||||||
|
bundle_ids.each do |bundle_id|
|
||||||
|
ohai "Quitting application ID #{bundle_id}"
|
||||||
|
next if running_processes(bundle_id).empty?
|
||||||
|
@command.run!("/usr/bin/osascript", args: ["-e", %Q(tell application id "#{bundle_id}" to quit)], sudo: true)
|
||||||
|
|
||||||
|
begin
|
||||||
|
Timeout.timeout(3) do
|
||||||
|
Kernel.loop do
|
||||||
|
break if running_processes(bundle_id).empty?
|
||||||
|
end
|
||||||
|
end
|
||||||
|
rescue Timeout::Error
|
||||||
|
next
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# :signal should come after :quit so it can be used as a backup when :quit fails
|
||||||
|
def uninstall_signal(*signals)
|
||||||
|
signals.flatten.each_slice(2) do |pair|
|
||||||
|
unless pair.size == 2
|
||||||
|
raise CaskInvalidError.new(@cask, "Each #{stanza} :signal must consist of 2 elements.")
|
||||||
|
end
|
||||||
|
|
||||||
|
signal, bundle_id = pair
|
||||||
|
ohai "Signalling '#{signal}' to application ID '#{bundle_id}'"
|
||||||
|
pids = running_processes(bundle_id).map(&:first)
|
||||||
|
next unless pids.any?
|
||||||
|
# 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.
|
||||||
|
odebug "Unix ids are #{pids.inspect} for processes with bundle identifier #{bundle_id}"
|
||||||
|
Process.kill(signal, *pids)
|
||||||
|
sleep 3
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def uninstall_login_item(*login_items)
|
||||||
|
login_items.each do |name|
|
||||||
ohai "Removing login item #{name}"
|
ohai "Removing login item #{name}"
|
||||||
@command.run!("/usr/bin/osascript",
|
@command.run!("/usr/bin/osascript",
|
||||||
args: ["-e", %Q(tell application "System Events" to delete every login item whose name is "#{name}")],
|
args: ["-e", %Q(tell application "System Events" to delete every login item whose name is "#{name}")],
|
||||||
@ -187,8 +144,8 @@ module Hbc
|
|||||||
end
|
end
|
||||||
|
|
||||||
# :kext should be unloaded before attempting to delete the relevant file
|
# :kext should be unloaded before attempting to delete the relevant file
|
||||||
def uninstall_kext(directives)
|
def uninstall_kext(*kexts)
|
||||||
Array(directives[:kext]).each do |kext|
|
kexts.each do |kext|
|
||||||
ohai "Unloading kernel extension #{kext}"
|
ohai "Unloading kernel extension #{kext}"
|
||||||
is_loaded = @command.run!("/usr/sbin/kextstat", args: ["-l", "-b", kext], sudo: true).stdout
|
is_loaded = @command.run!("/usr/sbin/kextstat", args: ["-l", "-b", kext], sudo: true).stdout
|
||||||
if is_loaded.length > 1
|
if is_loaded.length > 1
|
||||||
@ -209,6 +166,7 @@ module Hbc
|
|||||||
{ must_succeed: true, sudo: true },
|
{ must_succeed: true, sudo: true },
|
||||||
{ print_stdout: true },
|
{ print_stdout: true },
|
||||||
directive_name)
|
directive_name)
|
||||||
|
|
||||||
ohai "Running uninstall script #{executable}"
|
ohai "Running uninstall script #{executable}"
|
||||||
raise CaskInvalidError.new(@cask, "#{stanza} :#{directive_name} without :executable.") if executable.nil?
|
raise CaskInvalidError.new(@cask, "#{stanza} :#{directive_name} without :executable.") if executable.nil?
|
||||||
executable_path = @cask.staged_path.join(executable)
|
executable_path = @cask.staged_path.join(executable)
|
||||||
@ -225,43 +183,67 @@ module Hbc
|
|||||||
sleep 1
|
sleep 1
|
||||||
end
|
end
|
||||||
|
|
||||||
def uninstall_pkgutil(directives)
|
def uninstall_pkgutil(*pkgs)
|
||||||
ohai "Removing files from pkgutil Bill-of-Materials"
|
ohai "Uninstalling packages:"
|
||||||
Array(directives[:pkgutil]).each do |regexp|
|
pkgs.each do |regex|
|
||||||
pkgs = Hbc::Pkg.all_matching(regexp, @command)
|
Hbc::Pkg.all_matching(regex, @command).each do |pkg|
|
||||||
pkgs.each(&:uninstall)
|
puts pkg.package_id
|
||||||
|
pkg.uninstall
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def uninstall_delete(directives, expand_tilde = true)
|
def each_resolved_path(action, paths)
|
||||||
Array(directives[:delete]).concat(Array(directives[:trash])).flatten.each_slice(PATH_ARG_SLICE_SIZE) do |path_slice|
|
paths.each do |path|
|
||||||
ohai "Removing files: #{path_slice.utf8_inspect}"
|
resolved_path = Pathname.new(path)
|
||||||
path_slice = self.class.prepare_path_strings(:delete, path_slice, expand_tilde)
|
|
||||||
@command.run!("/bin/rm", args: path_slice.unshift("-rf", "--"), sudo: true)
|
if path.start_with?("~")
|
||||||
|
resolved_path = resolved_path.expand_path
|
||||||
|
end
|
||||||
|
|
||||||
|
if resolved_path.relative? || resolved_path.split.any? { |part| part.to_s == ".." }
|
||||||
|
opoo "Skipping #{Formatter.identifier(action)} for relative path '#{path}'."
|
||||||
|
next
|
||||||
|
end
|
||||||
|
|
||||||
|
if MacOS.undeletable?(resolved_path)
|
||||||
|
opoo "Skipping #{Formatter.identifier(action)} for undeletable path '#{path}'."
|
||||||
|
next
|
||||||
|
end
|
||||||
|
|
||||||
|
yield path, Pathname.glob(resolved_path)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# :trash functionality is stubbed as a synonym for :delete
|
def uninstall_delete(*paths)
|
||||||
# TODO: make :trash work differently, moving files to the Trash
|
return if paths.empty?
|
||||||
def uninstall_trash(directives, expand_tilde = true)
|
|
||||||
uninstall_delete(directives, expand_tilde)
|
ohai "Removing files:"
|
||||||
|
each_resolved_path(:delete, paths) do |path, resolved_paths|
|
||||||
|
puts path
|
||||||
|
@command.run!("/usr/bin/xargs", args: ["-0", "--", "/bin/rm", "-r", "-f", "--"], input: resolved_paths.join("\0"), sudo: true)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def uninstall_rmdir(directories, expand_tilde = true)
|
def uninstall_trash(*paths)
|
||||||
action = :rmdir
|
# :trash functionality is stubbed as a synonym for :delete
|
||||||
self.class.prepare_path_strings(action, Array(directories[action]).flatten, expand_tilde).each do |directory|
|
# TODO: make :trash work differently, moving files to the Trash
|
||||||
next if directory.to_s.empty?
|
uninstall_delete(*paths)
|
||||||
ohai "Removing directory if empty: #{directory.to_s.utf8_inspect}"
|
end
|
||||||
directory = Pathname.new(directory)
|
|
||||||
next unless directory.exist?
|
def uninstall_rmdir(*directories)
|
||||||
@command.run!("/bin/rm",
|
return if directories.empty?
|
||||||
args: ["-f", "--", directory.join(".DS_Store")],
|
|
||||||
sudo: true,
|
ohai "Removing directories if empty:"
|
||||||
print_stderr: false)
|
each_resolved_path(:rmdir, directories) do |path, resolved_paths|
|
||||||
@command.run("/bin/rmdir",
|
puts path
|
||||||
args: ["--", directory],
|
resolved_paths.select(&:directory?).each do |resolved_path|
|
||||||
sudo: true,
|
if (ds_store = resolved_path.join(".DS_Store")).exist?
|
||||||
print_stderr: false)
|
@command.run!("/bin/rm", args: ["-f", "--", ds_store], sudo: true, print_stderr: false)
|
||||||
|
end
|
||||||
|
|
||||||
|
@command.run("/bin/rmdir", args: ["--", resolved_path], sudo: true, print_stderr: false)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -4,7 +4,7 @@ module Hbc
|
|||||||
module Artifact
|
module Artifact
|
||||||
class Zap < UninstallBase
|
class Zap < UninstallBase
|
||||||
def zap_phase
|
def zap_phase
|
||||||
dispatch_uninstall_directives(expand_tilde: true)
|
dispatch_uninstall_directives
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -1,351 +1,7 @@
|
|||||||
|
require_relative "uninstall_zap_shared_examples"
|
||||||
|
|
||||||
describe Hbc::Artifact::Uninstall, :cask do
|
describe Hbc::Artifact::Uninstall, :cask do
|
||||||
let(:cask) { Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/with-installable.rb") }
|
describe "#uninstall_phase" do
|
||||||
|
include_examples "#uninstall_phase or #zap_phase"
|
||||||
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
|
||||||
end
|
end
|
||||||
|
|||||||
@ -0,0 +1,334 @@
|
|||||||
|
shared_examples "#uninstall_phase or #zap_phase" do
|
||||||
|
let(:artifact_name) { described_class.artifact_name }
|
||||||
|
let(:artifact) { described_class.new(cask, command: fake_system_command) }
|
||||||
|
let(:fake_system_command) { Hbc::FakeSystemCommand }
|
||||||
|
|
||||||
|
subject do
|
||||||
|
shutup do
|
||||||
|
artifact.public_send(:"#{artifact_name}_phase")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "using :launchctl" do
|
||||||
|
let(:cask) { Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/with-#{artifact_name}-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) do
|
||||||
|
<<-EOS.undent
|
||||||
|
{
|
||||||
|
"LimitLoadToSessionType" = "Aqua";
|
||||||
|
"Label" = "my.fancy.package.service";
|
||||||
|
"TimeOut" = 30;
|
||||||
|
"OnDemand" = true;
|
||||||
|
"LastExitStatus" = 0;
|
||||||
|
"ProgramArguments" = (
|
||||||
|
"argument";
|
||||||
|
);
|
||||||
|
};
|
||||||
|
EOS
|
||||||
|
end
|
||||||
|
|
||||||
|
it "works when job is owned by user" 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
|
||||||
|
|
||||||
|
it "works when job is owned by system" 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
|
||||||
|
|
||||||
|
context "using :pkgutil" do
|
||||||
|
let(:fake_system_command) { class_double(Hbc::SystemCommand) }
|
||||||
|
|
||||||
|
let(:cask) { Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/with-#{artifact_name}-pkgutil.rb") }
|
||||||
|
let(:main_pkg_id) { "my.fancy.package.main" }
|
||||||
|
let(:agent_pkg_id) { "my.fancy.package.agent" }
|
||||||
|
let(:main_files) do
|
||||||
|
%w[
|
||||||
|
fancy/bin/fancy.exe
|
||||||
|
fancy/var/fancy.data
|
||||||
|
]
|
||||||
|
end
|
||||||
|
let(:main_dirs) do
|
||||||
|
%w[
|
||||||
|
fancy
|
||||||
|
fancy/bin
|
||||||
|
fancy/var
|
||||||
|
]
|
||||||
|
end
|
||||||
|
let(:agent_files) do
|
||||||
|
%w[
|
||||||
|
fancy/agent/fancy-agent.exe
|
||||||
|
fancy/agent/fancy-agent.pid
|
||||||
|
fancy/agent/fancy-agent.log
|
||||||
|
]
|
||||||
|
end
|
||||||
|
let(:agent_dirs) do
|
||||||
|
%w[
|
||||||
|
fancy
|
||||||
|
fancy/agent
|
||||||
|
]
|
||||||
|
end
|
||||||
|
let(:pkg_info_plist) do
|
||||||
|
<<-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
|
||||||
|
end
|
||||||
|
|
||||||
|
it "is supported" do
|
||||||
|
allow(fake_system_command).to receive(:run).with(
|
||||||
|
"/usr/sbin/pkgutil",
|
||||||
|
args: ["--pkgs=my.fancy.package.*"],
|
||||||
|
).and_return(double(stdout: "#{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|
|
||||||
|
|
||||||
|
allow(fake_system_command).to receive(:run!).with(
|
||||||
|
"/usr/sbin/pkgutil",
|
||||||
|
args: ["--only-files", "--files", pkg_id.to_s],
|
||||||
|
).and_return(double(stdout: pkg_files.join("\n")))
|
||||||
|
|
||||||
|
allow(fake_system_command).to receive(:run!).with(
|
||||||
|
"/usr/sbin/pkgutil",
|
||||||
|
args: ["--only-dirs", "--files", pkg_id.to_s],
|
||||||
|
).and_return(double(stdout: pkg_dirs.join("\n")))
|
||||||
|
|
||||||
|
allow(fake_system_command).to receive(:run!).with(
|
||||||
|
"/usr/sbin/pkgutil",
|
||||||
|
args: ["--files", pkg_id.to_s],
|
||||||
|
).and_return(double(stdout: (pkg_files + pkg_dirs).join("\n")))
|
||||||
|
|
||||||
|
result = Hbc::SystemCommand::Result.new(nil, pkg_info_plist, nil, 0)
|
||||||
|
allow(fake_system_command).to receive(:run!).with(
|
||||||
|
"/usr/sbin/pkgutil",
|
||||||
|
args: ["--pkg-info-plist", pkg_id.to_s],
|
||||||
|
).and_return(result)
|
||||||
|
|
||||||
|
expect(fake_system_command).to receive(:run).with(
|
||||||
|
"/usr/bin/xargs",
|
||||||
|
args: ["-0", "--", "/bin/rm", "-f", "--"],
|
||||||
|
input: pkg_files.map { |path| "/tmp/#{path}" }.join("\0"),
|
||||||
|
sudo: true,
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(fake_system_command).to receive(:run!).with(
|
||||||
|
"/usr/sbin/pkgutil",
|
||||||
|
args: ["--forget", pkg_id.to_s],
|
||||||
|
sudo: true,
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
subject
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "using :kext" do
|
||||||
|
let(:cask) { Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/with-#{artifact_name}-kext.rb") }
|
||||||
|
let(:kext_id) { "my.fancy.package.kernelextension" }
|
||||||
|
|
||||||
|
it "is supported" 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 "using :quit" do
|
||||||
|
let(:cask) { Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/with-#{artifact_name}-quit.rb") }
|
||||||
|
let(:bundle_id) { "my.fancy.package.app" }
|
||||||
|
let(:quit_application_script) do
|
||||||
|
%Q(tell application id "#{bundle_id}" to quit)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "is supported" 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 "using :signal" do
|
||||||
|
let(:cask) { Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/with-#{artifact_name}-signal.rb") }
|
||||||
|
let(:bundle_id) { "my.fancy.package.app" }
|
||||||
|
let(:signals) { %w[TERM KILL] }
|
||||||
|
let(:unix_pids) { [12_345, 67_890] }
|
||||||
|
|
||||||
|
it "is supported" 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
|
||||||
|
|
||||||
|
[:delete, :trash].each do |directive|
|
||||||
|
context "using :#{directive}" do
|
||||||
|
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") }
|
||||||
|
let(:paths) { [absolute_path, path_with_tilde, glob_path1, glob_path2] }
|
||||||
|
|
||||||
|
around(:each) do |example|
|
||||||
|
begin
|
||||||
|
ENV["HOME"] = dir
|
||||||
|
|
||||||
|
FileUtils.touch paths
|
||||||
|
|
||||||
|
example.run
|
||||||
|
ensure
|
||||||
|
FileUtils.rm_f paths
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:fake_system_command) { Hbc::NeverSudoSystemCommand }
|
||||||
|
let(:cask) { Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/with-#{artifact_name}-#{directive}.rb") }
|
||||||
|
|
||||||
|
it "is supported" do
|
||||||
|
paths.each do |path|
|
||||||
|
expect(path).to exist
|
||||||
|
end
|
||||||
|
|
||||||
|
subject
|
||||||
|
|
||||||
|
paths.each do |path|
|
||||||
|
expect(path).not_to exist
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "using :rmdir" do
|
||||||
|
let(:fake_system_command) { Hbc::NeverSudoSystemCommand }
|
||||||
|
let(:cask) { Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/with-#{artifact_name}-rmdir.rb") }
|
||||||
|
let(:empty_directory) { Pathname.new("#{TEST_TMPDIR}/empty_directory_path") }
|
||||||
|
let(:ds_store) { empty_directory.join(".DS_Store") }
|
||||||
|
|
||||||
|
before(:each) do
|
||||||
|
empty_directory.mkdir
|
||||||
|
FileUtils.touch ds_store
|
||||||
|
end
|
||||||
|
|
||||||
|
after(:each) do
|
||||||
|
FileUtils.rm_rf empty_directory
|
||||||
|
end
|
||||||
|
|
||||||
|
it "is supported" do
|
||||||
|
expect(empty_directory).to exist
|
||||||
|
expect(ds_store).to exist
|
||||||
|
|
||||||
|
subject
|
||||||
|
|
||||||
|
expect(ds_store).not_to exist
|
||||||
|
expect(empty_directory).not_to exist
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "using :script" do
|
||||||
|
let(:cask) { Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/with-#{artifact_name}-script.rb") }
|
||||||
|
let(:script_pathname) { cask.staged_path.join("MyFancyPkg", "FancyUninstaller.tool") }
|
||||||
|
|
||||||
|
it "is supported" do
|
||||||
|
Hbc::FakeSystemCommand.expects_command(%w[/bin/chmod -- +x] + [script_pathname])
|
||||||
|
|
||||||
|
Hbc::FakeSystemCommand.expects_command(
|
||||||
|
sudo(cask.staged_path.join("MyFancyPkg", "FancyUninstaller.tool"), "--please"),
|
||||||
|
)
|
||||||
|
|
||||||
|
InstallHelper.install_without_artifacts(cask)
|
||||||
|
subject
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "using :early_script" do
|
||||||
|
let(:cask) { Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/with-#{artifact_name}-early-script.rb") }
|
||||||
|
let(:script_pathname) { cask.staged_path.join("MyFancyPkg", "FancyUninstaller.tool") }
|
||||||
|
|
||||||
|
it "is supported" do
|
||||||
|
Hbc::FakeSystemCommand.expects_command(%w[/bin/chmod -- +x] + [script_pathname])
|
||||||
|
|
||||||
|
Hbc::FakeSystemCommand.expects_command(
|
||||||
|
sudo(cask.staged_path.join("MyFancyPkg", "FancyUninstaller.tool"), "--please"),
|
||||||
|
)
|
||||||
|
|
||||||
|
InstallHelper.install_without_artifacts(cask)
|
||||||
|
subject
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "using :login_item" do
|
||||||
|
let(:cask) { Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/with-#{artifact_name}-login-item.rb") }
|
||||||
|
|
||||||
|
it "is supported" 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
|
||||||
@ -1,352 +1,7 @@
|
|||||||
# TODO: test that zap removes an alternate version of the same Cask
|
require_relative "uninstall_zap_shared_examples"
|
||||||
|
|
||||||
describe Hbc::Artifact::Zap, :cask do
|
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
|
describe "#zap_phase" do
|
||||||
subject {
|
include_examples "#uninstall_phase or #zap_phase"
|
||||||
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
|
||||||
end
|
end
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user