brew/Library/Homebrew/test/bundle/brew_installer_spec.rb

595 lines
24 KiB
Ruby
Raw Normal View History

# frozen_string_literal: true
require "bundle"
require "formula"
require "bundle/formula_installer"
require "bundle/formula_dumper"
require "bundle/brew_services"
RSpec.describe Homebrew::Bundle::FormulaInstaller do
let(:formula_name) { "mysql" }
let(:options) { { args: ["with-option"] } }
let(:installer) { described_class.new(formula_name, options) }
before do
# don't try to load gcc/glibc
allow(DevelopmentTools).to receive_messages(needs_libc_formula?: false, needs_compiler_formula?: false)
stub_formula_loader formula(formula_name) { url "mysql-1.0" }
end
context "when the formula is installed" do
before do
allow_any_instance_of(described_class).to receive(:installed?).and_return(true)
end
context "with a true start_service option" do
before do
allow_any_instance_of(described_class).to receive(:install_change_state!).and_return(true)
allow_any_instance_of(described_class).to receive(:installed?).and_return(true)
end
context "when service is already running" do
before do
allow(Homebrew::Bundle::BrewServices).to receive(:started?).with(formula_name).and_return(true)
end
context "with a successful installation" do
it "start service" do
expect(Homebrew::Bundle::BrewServices).not_to receive(:start)
described_class.preinstall(formula_name, start_service: true)
described_class.install(formula_name, start_service: true)
end
end
context "with a skipped installation" do
it "start service" do
expect(Homebrew::Bundle::BrewServices).not_to receive(:start)
described_class.install(formula_name, preinstall: false, start_service: true)
end
end
end
context "when service is not running" do
before do
allow(Homebrew::Bundle::BrewServices).to receive(:started?).with(formula_name).and_return(false)
end
context "with a successful installation" do
it "start service" do
expect(Homebrew::Bundle::BrewServices).to \
receive(:start).with(formula_name, file: nil, verbose: false).and_return(true)
described_class.preinstall(formula_name, start_service: true)
described_class.install(formula_name, start_service: true)
end
end
context "with a skipped installation" do
it "start service" do
expect(Homebrew::Bundle::BrewServices).to \
receive(:start).with(formula_name, file: nil, verbose: false).and_return(true)
described_class.install(formula_name, preinstall: false, start_service: true)
end
end
end
end
context "with an always restart_service option" do
before do
allow_any_instance_of(described_class).to receive(:install_change_state!).and_return(true)
allow_any_instance_of(described_class).to receive(:installed?).and_return(true)
end
context "with a successful installation" do
it "restart service" do
expect(Homebrew::Bundle::BrewServices).to \
receive(:restart).with(formula_name, file: nil, verbose: false).and_return(true)
described_class.preinstall(formula_name, restart_service: :always)
described_class.install(formula_name, restart_service: :always)
end
end
context "with a skipped installation" do
it "restart service" do
expect(Homebrew::Bundle::BrewServices).to \
receive(:restart).with(formula_name, file: nil, verbose: false).and_return(true)
described_class.install(formula_name, preinstall: false, restart_service: :always)
end
end
end
context "when the link option is true" do
before do
allow_any_instance_of(described_class).to receive(:install_change_state!).and_return(true)
end
it "links formula" do
allow_any_instance_of(described_class).to receive(:linked?).and_return(false)
expect(Homebrew::Bundle).to receive(:system).with(HOMEBREW_BREW_FILE, "link", "mysql",
verbose: false).and_return(true)
described_class.preinstall(formula_name, link: true)
described_class.install(formula_name, link: true)
end
it "force-links keg-only formula" do
allow_any_instance_of(described_class).to receive(:linked?).and_return(false)
allow_any_instance_of(described_class).to receive(:keg_only?).and_return(true)
expect(Homebrew::Bundle).to receive(:system).with(HOMEBREW_BREW_FILE, "link", "--force", "mysql",
verbose: false).and_return(true)
described_class.preinstall(formula_name, link: true)
described_class.install(formula_name, link: true)
end
end
context "when the link option is :overwrite" do
before do
allow_any_instance_of(described_class).to receive(:install_change_state!).and_return(true)
end
it "overwrite links formula" do
allow_any_instance_of(described_class).to receive(:linked?).and_return(false)
expect(Homebrew::Bundle).to receive(:system).with(HOMEBREW_BREW_FILE, "link", "--overwrite", "mysql",
verbose: false).and_return(true)
described_class.preinstall(formula_name, link: :overwrite)
described_class.install(formula_name, link: :overwrite)
end
end
context "when the link option is false" do
before do
allow_any_instance_of(described_class).to receive(:install_change_state!).and_return(true)
end
it "unlinks formula" do
allow_any_instance_of(described_class).to receive(:linked?).and_return(true)
expect(Homebrew::Bundle).to receive(:system).with(HOMEBREW_BREW_FILE, "unlink", "mysql",
verbose: false).and_return(true)
described_class.preinstall(formula_name, link: false)
described_class.install(formula_name, link: false)
end
end
context "when the link option is nil and formula is unlinked and not keg-only" do
before do
allow_any_instance_of(described_class).to receive(:install_change_state!).and_return(true)
allow_any_instance_of(described_class).to receive(:linked?).and_return(false)
allow_any_instance_of(described_class).to receive(:keg_only?).and_return(false)
end
it "links formula" do
expect(Homebrew::Bundle).to receive(:system).with(HOMEBREW_BREW_FILE, "link", "mysql",
verbose: false).and_return(true)
described_class.preinstall(formula_name, link: nil)
described_class.install(formula_name, link: nil)
end
end
context "when the link option is nil and formula is linked and keg-only" do
before do
allow_any_instance_of(described_class).to receive(:install_change_state!).and_return(true)
allow_any_instance_of(described_class).to receive(:linked?).and_return(true)
allow_any_instance_of(described_class).to receive(:keg_only?).and_return(true)
end
it "unlinks formula" do
expect(Homebrew::Bundle).to receive(:system).with(HOMEBREW_BREW_FILE, "unlink", "mysql",
verbose: false).and_return(true)
described_class.preinstall(formula_name, link: nil)
described_class.install(formula_name, link: nil)
end
end
context "when the conflicts_with option is provided" do
before do
allow(Homebrew::Bundle::FormulaDumper).to receive(:formulae_by_full_name).and_call_original
allow(Homebrew::Bundle::FormulaDumper).to receive(:formulae_by_full_name).with("mysql").and_return(
name: "mysql",
conflicts_with: ["mysql55"],
)
allow(described_class).to receive(:formula_installed?).and_return(true)
allow_any_instance_of(described_class).to receive(:install!).and_return(true)
allow_any_instance_of(described_class).to receive(:upgrade!).and_return(true)
end
it "unlinks conflicts and stops their services" do
verbose = false
allow_any_instance_of(described_class).to receive(:linked?).and_return(true)
expect(Homebrew::Bundle).to receive(:system).with(HOMEBREW_BREW_FILE, "unlink", "mysql55",
verbose:).and_return(true)
expect(Homebrew::Bundle).to receive(:system).with(HOMEBREW_BREW_FILE, "unlink", "mysql56",
verbose:).and_return(true)
expect(Homebrew::Bundle::BrewServices).to receive(:stop).with("mysql55", verbose:).and_return(true)
expect(Homebrew::Bundle::BrewServices).to receive(:stop).with("mysql56", verbose:).and_return(true)
expect(Homebrew::Bundle::BrewServices).to receive(:restart).with(formula_name, file: nil,
verbose:).and_return(true)
described_class.preinstall(formula_name, restart_service: :always, conflicts_with: ["mysql56"])
described_class.install(formula_name, restart_service: :always, conflicts_with: ["mysql56"])
end
it "prints a message" do
allow_any_instance_of(described_class).to receive(:linked?).and_return(true)
allow_any_instance_of(described_class).to receive(:puts)
verbose = true
expect(Homebrew::Bundle).to receive(:system).with(HOMEBREW_BREW_FILE, "unlink", "mysql55",
verbose:).and_return(true)
expect(Homebrew::Bundle).to receive(:system).with(HOMEBREW_BREW_FILE, "unlink", "mysql56",
verbose:).and_return(true)
expect(Homebrew::Bundle::BrewServices).to receive(:stop).with("mysql55", verbose:).and_return(true)
expect(Homebrew::Bundle::BrewServices).to receive(:stop).with("mysql56", verbose:).and_return(true)
expect(Homebrew::Bundle::BrewServices).to receive(:restart).with(formula_name, file: nil,
verbose:).and_return(true)
described_class.preinstall(formula_name, restart_service: :always, conflicts_with: ["mysql56"], verbose: true)
described_class.install(formula_name, restart_service: :always, conflicts_with: ["mysql56"], verbose: true)
end
end
context "when the postinstall option is provided" do
before do
allow_any_instance_of(described_class).to receive(:install_change_state!).and_return(true)
allow_any_instance_of(described_class).to receive(:installed?).and_return(true)
end
context "when formula has changed" do
before do
allow_any_instance_of(described_class).to receive(:changed?).and_return(true)
end
it "runs the postinstall command" do
expect(Kernel).to receive(:system).with("custom command").and_return(true)
described_class.preinstall(formula_name, postinstall: "custom command")
described_class.install(formula_name, postinstall: "custom command")
end
it "reports a failure" do
expect(Kernel).to receive(:system).with("custom command").and_return(false)
described_class.preinstall(formula_name, postinstall: "custom command")
expect(described_class.install(formula_name, postinstall: "custom command")).to be(false)
end
end
context "when formula has not changed" do
before do
allow_any_instance_of(described_class).to receive(:changed?).and_return(false)
end
it "does not run the postinstall command" do
expect(Kernel).not_to receive(:system)
described_class.preinstall(formula_name, postinstall: "custom command")
described_class.install(formula_name, postinstall: "custom command")
end
end
end
context "when the version_file option is provided" do
before do
Homebrew::Bundle.reset!
allow_any_instance_of(described_class).to receive(:install_change_state!).and_return(true)
allow_any_instance_of(described_class).to receive(:installed?).and_return(true)
allow_any_instance_of(described_class).to receive(:linked?).and_return(true)
end
let(:version_file) { "version.txt" }
let(:version) { "1.0" }
context "when formula versions are changed and specified by the environment" do
before do
allow_any_instance_of(described_class).to receive(:changed?).and_return(false)
ENV["HOMEBREW_BUNDLE_EXEC_FORMULA_VERSION_#{formula_name.upcase}"] = version
end
it "writes the version to the file" do
expect(File).to receive(:write).with(version_file, "#{version}\n")
described_class.preinstall(formula_name, version_file:)
described_class.install(formula_name, version_file:)
end
end
context "when using the latest formula" do
it "writes the version to the file" do
expect(File).to receive(:write).with(version_file, "#{version}\n")
described_class.preinstall(formula_name, version_file:)
described_class.install(formula_name, version_file:)
end
end
end
end
context "when a formula isn't installed" do
before do
allow_any_instance_of(described_class).to receive(:installed?).and_return(false)
allow_any_instance_of(described_class).to receive(:install_change_state!).and_return(false)
end
it "did not call restart service" do
expect(Homebrew::Bundle::BrewServices).not_to receive(:restart)
described_class.preinstall(formula_name, restart_service: true)
end
end
describe ".outdated_formulae" do
it "calls Homebrew" do
described_class.reset!
expect(Homebrew::Bundle::FormulaDumper).to receive(:formulae).and_return(
[
{ name: "a", outdated?: true },
{ name: "b", outdated?: true },
{ name: "c", outdated?: false },
],
)
expect(described_class.outdated_formulae).to eql(%w[a b])
end
end
describe ".pinned_formulae" do
it "calls Homebrew" do
described_class.reset!
expect(Homebrew::Bundle::FormulaDumper).to receive(:formulae).and_return(
[
{ name: "a", pinned?: true },
{ name: "b", pinned?: true },
{ name: "c", pinned?: false },
],
)
expect(described_class.pinned_formulae).to eql(%w[a b])
end
end
describe ".formula_installed_and_up_to_date?" do
before do
Homebrew::Bundle::FormulaDumper.reset!
described_class.reset!
allow(described_class).to receive(:outdated_formulae).and_return(%w[bar])
allow_any_instance_of(Formula).to receive(:outdated?).and_return(true)
allow(Homebrew::Bundle::FormulaDumper).to receive(:formulae).and_return [
{
name: "foo",
full_name: "homebrew/tap/foo",
aliases: ["foobar"],
args: [],
version: "1.0",
dependencies: [],
requirements: [],
},
{
name: "bar",
full_name: "bar",
aliases: [],
args: [],
version: "1.0",
dependencies: [],
requirements: [],
},
]
stub_formula_loader formula("foo") { url "foo-1.0" }
stub_formula_loader formula("bar") { url "bar-1.0" }
end
it "returns result" do
expect(described_class.formula_installed_and_up_to_date?("foo")).to be(true)
expect(described_class.formula_installed_and_up_to_date?("foobar")).to be(true)
expect(described_class.formula_installed_and_up_to_date?("bar")).to be(false)
expect(described_class.formula_installed_and_up_to_date?("baz")).to be(false)
end
end
context "when brew is installed" do
context "when no formula is installed" do
before do
allow(described_class).to receive(:installed_formulae).and_return([])
allow_any_instance_of(described_class).to receive(:conflicts_with).and_return([])
allow_any_instance_of(described_class).to receive(:linked?).and_return(true)
end
it "install formula" do
expect(Homebrew::Bundle).to receive(:system)
.with(HOMEBREW_BREW_FILE, "install", "--formula", formula_name, "--with-option", verbose: false)
.and_return(true)
expect(installer.preinstall).to be(true)
expect(installer.install).to be(true)
end
it "reports a failure" do
expect(Homebrew::Bundle).to receive(:system)
.with(HOMEBREW_BREW_FILE, "install", "--formula", formula_name, "--with-option", verbose: false)
.and_return(false)
expect(installer.preinstall).to be(true)
expect(installer.install).to be(false)
end
end
context "when formula is installed" do
before do
allow(described_class).to receive(:installed_formulae).and_return([formula_name])
allow_any_instance_of(described_class).to receive(:conflicts_with).and_return([])
allow_any_instance_of(described_class).to receive(:linked?).and_return(true)
allow_any_instance_of(Formula).to receive(:outdated?).and_return(true)
end
context "when formula upgradable" do
before do
allow(described_class).to receive(:outdated_formulae).and_return([formula_name])
end
it "upgrade formula" do
expect(Homebrew::Bundle).to \
receive(:system).with(HOMEBREW_BREW_FILE, "upgrade", "--formula", formula_name, verbose: false)
.and_return(true)
expect(installer.preinstall).to be(true)
expect(installer.install).to be(true)
end
it "reports a failure" do
expect(Homebrew::Bundle).to \
receive(:system).with(HOMEBREW_BREW_FILE, "upgrade", "--formula", formula_name, verbose: false)
.and_return(false)
expect(installer.preinstall).to be(true)
expect(installer.install).to be(false)
end
context "when formula pinned" do
before do
allow(described_class).to receive(:pinned_formulae).and_return([formula_name])
end
it "does not upgrade formula" do
expect(Homebrew::Bundle).not_to \
receive(:system).with(HOMEBREW_BREW_FILE, "upgrade", "--formula", formula_name, verbose: false)
expect(installer.preinstall).to be(false)
end
end
context "when formula not upgraded" do
before do
allow(described_class).to receive(:outdated_formulae).and_return([])
end
it "does not upgrade formula" do
expect(Homebrew::Bundle).not_to receive(:system)
expect(installer.preinstall).to be(false)
end
end
end
end
end
describe "#changed?" do
it "is false by default" do
expect(described_class.new(formula_name).changed?).to be(false)
end
end
describe "#start_service?" do
it "is false by default" do
expect(described_class.new(formula_name).start_service?).to be(false)
end
context "when the start_service option is true" do
it "is true" do
expect(described_class.new(formula_name, start_service: true).start_service?).to be(true)
end
end
end
describe "#start_service_needed?" do
context "when a service is already started" do
before do
allow(Homebrew::Bundle::BrewServices).to receive(:started?).with(formula_name).and_return(true)
end
it "is false by default" do
expect(described_class.new(formula_name).start_service_needed?).to be(false)
end
it "is false with {start_service: true}" do
expect(described_class.new(formula_name, start_service: true).start_service_needed?).to be(false)
end
it "is false with {restart_service: true}" do
expect(described_class.new(formula_name, restart_service: true).start_service_needed?).to be(false)
end
it "is false with {restart_service: :changed}" do
expect(described_class.new(formula_name, restart_service: :changed).start_service_needed?).to be(false)
end
it "is false with {restart_service: :always}" do
expect(described_class.new(formula_name, restart_service: :always).start_service_needed?).to be(false)
end
end
context "when a service is not started" do
before do
allow(Homebrew::Bundle::BrewServices).to receive(:started?).with(formula_name).and_return(false)
end
it "is false by default" do
expect(described_class.new(formula_name).start_service_needed?).to be(false)
end
it "is true if {start_service: true}" do
expect(described_class.new(formula_name, start_service: true).start_service_needed?).to be(true)
end
it "is true if {restart_service: true}" do
expect(described_class.new(formula_name, restart_service: true).start_service_needed?).to be(true)
end
it "is true if {restart_service: :changed}" do
expect(described_class.new(formula_name, restart_service: :changed).start_service_needed?).to be(true)
end
it "is true if {restart_service: :always}" do
expect(described_class.new(formula_name, restart_service: :always).start_service_needed?).to be(true)
end
end
end
describe "#restart_service?" do
it "is false by default" do
expect(described_class.new(formula_name).restart_service?).to be(false)
end
context "when the restart_service option is true" do
it "is true" do
expect(described_class.new(formula_name, restart_service: true).restart_service?).to be(true)
end
end
context "when the restart_service option is always" do
it "is true" do
expect(described_class.new(formula_name, restart_service: :always).restart_service?).to be(true)
end
end
context "when the restart_service option is changed" do
it "is true" do
expect(described_class.new(formula_name, restart_service: :changed).restart_service?).to be(true)
end
end
end
describe "#restart_service_needed?" do
it "is false by default" do
expect(described_class.new(formula_name).restart_service_needed?).to be(false)
end
context "when a service is unchanged" do
before do
allow_any_instance_of(described_class).to receive(:changed?).and_return(false)
end
it "is false with {restart_service: true}" do
expect(described_class.new(formula_name, restart_service: true).restart_service_needed?).to be(false)
end
it "is true with {restart_service: :always}" do
expect(described_class.new(formula_name, restart_service: :always).restart_service_needed?).to be(true)
end
it "is false if {restart_service: :changed}" do
expect(described_class.new(formula_name, restart_service: :changed).restart_service_needed?).to be(false)
end
end
context "when a service is changed" do
before do
allow_any_instance_of(described_class).to receive(:changed?).and_return(true)
end
it "is true with {restart_service: true}" do
expect(described_class.new(formula_name, restart_service: true).restart_service_needed?).to be(true)
end
it "is true with {restart_service: :always}" do
expect(described_class.new(formula_name, restart_service: :always).restart_service_needed?).to be(true)
end
it "is true if {restart_service: :changed}" do
expect(described_class.new(formula_name, restart_service: :changed).restart_service_needed?).to be(true)
end
end
end
end