Files
brew/Library/Homebrew/test/formula_installer_spec.rb
T
Mike McQuaid 5e4dba4d6b Remove the formula install-step var default
Formula install-step paths now serialise only a base that was explicitly
specified. RuboCop prevents relative official-tap paths from relying on the
current working directory.

Run structured-only API post-installs from the current JSON data so old
formula snapshots embedded in bottles cannot restore the removed default.
Keep using bottle snapshots for formulae that still have Ruby hooks.
2026-08-06 08:30:57 +01:00

1530 lines
54 KiB
Ruby

# typed: false
# frozen_string_literal: true
require "formula"
require "formula_installer"
require "keg"
require "sandbox"
require "tab"
require "trust"
require "cmd/install"
require "test/support/fixtures/testball"
require "test/support/fixtures/testball_bottle"
require "test/support/fixtures/failball"
require "test/support/fixtures/failball_offline_install"
RSpec.describe FormulaInstaller do
matcher :be_poured_from_bottle do
match(&:poured_from_bottle)
end
def temporary_install(formula, **options)
expect(formula).not_to be_latest_version_installed
installer = FormulaInstaller.new(formula, **options)
with_env(HOMEBREW_NO_INSTALL_FROM_API: "1") do
installer.fetch
installer.install
end
keg = Keg.new(formula.prefix)
expect(formula).to be_latest_version_installed
begin
Tab.clear_cache
expect(keg.tab).not_to be_poured_from_bottle
yield formula if block_given?
ensure
Tab.clear_cache
keg.unlink
keg.uninstall
formula.clear_cache
# there will be log files when sandbox is enable.
FileUtils.rm_r(formula.logs) if formula.logs.directory?
end
expect(keg).not_to exist
expect(formula).not_to be_latest_version_installed
end
specify "basic installation" do
temporary_install(Testball.new) do |f|
# Test that things made it into the Keg
# "readme" is empty, so it should not be installed
expect(f.prefix/"readme").not_to exist
expect(f.bin).to be_a_directory
expect(f.bin.children.count).to eq(3)
expect(f.libexec).to be_a_directory
expect(f.libexec.children.count).to eq(1)
expect(f.prefix/"main.c").not_to exist
expect(f.prefix/"license").not_to exist
# Test that things make it into the Cellar
keg = Keg.new f.prefix
keg.link
bin = HOMEBREW_PREFIX/"bin"
expect(bin).to be_a_directory
expect(bin.children.count).to eq(3)
expect(f.prefix/".brew/testball.rb").to be_readable
end
end
specify "offline installation" do
expect { temporary_install(FailballOfflineInstall.new) }.to raise_error(BuildError) if Sandbox.available?
end
specify "Formula is not poured from bottle when compiler specified" do
temporary_install(TestballBottle.new, cc: "clang") do |f|
tab = Tab.for_formula(f)
expect(tab.compiler).to eq("clang")
end
end
specify "relocated bottle install does not require developer tools on Apple Silicon", :needs_macos do
formula = TestballBottle.new
installer = described_class.new(formula)
allow(installer).to receive_messages(lock: nil, pour_bottle?: true, quiet?: true)
allow(Hardware::CPU).to receive(:arm?).and_return(true)
allow(formula.bottle_specification).to receive(:skip_relocation?).and_return(false)
expect(Homebrew::Diagnostic::Checks).not_to receive(:new)
allow(installer).to receive(:check_conflicts).and_raise("stopped after preinstall checks")
expect do
installer.install
end.to raise_error("stopped after preinstall checks")
end
describe "#finish" do
it "runs structured post-install work through the post-install subprocess" do
formula = formula "finish-install-steps" do
T.bind(self, T.class_of(Formula))
url "foo-1.0"
end
installer = described_class.new(formula)
tab = instance_double(Tab)
keg = instance_double(Keg, tab:)
allow(Keg).to receive(:new).with(formula.prefix).and_return(keg)
allow(installer).to receive_messages(
build_bottle?: false,
caveats: nil,
debug_symbols?: false,
fix_dynamic_linkage: nil,
install_service: nil,
link: nil,
link_manual_command_warning: nil,
only_deps?: false,
quiet?: true,
show_summary_heading?: false,
skip_post_install?: false,
summary: "summary",
verbose?: false,
)
allow(formula).to receive_messages(post_install_steps_defined?: true, post_install_defined?: false,
runtime_dependencies: [])
allow(CacheStoreDatabase).to receive(:use).with(:linkage)
allow(Homebrew::EnvConfig).to receive(:sbom?).and_return(false)
allow(Homebrew::Install).to receive(:global_post_install)
allow(Tab).to receive_messages(clear_cache: nil, runtime_deps_hash: [])
allow(tab).to receive(:runtime_dependencies=)
allow(tab).to receive(:write)
expect(formula).to receive(:install_etc_var).ordered
expect(formula).not_to receive(:run_post_install_steps)
expect(installer).to receive(:post_install).ordered
installer.finish
end
end
describe "#post_install" do
it "restores bin/brew after a Landlock-sandboxed post-install replaces it" do
prefix = mktmpdir
stub_const("HOMEBREW_PREFIX", prefix)
brew_file = prefix/"bin/brew"
original_brew_file = prefix/"Homebrew/bin/brew"
original_brew_file.dirname.mkpath
original_brew_file.write "#!/bin/sh\n"
brew_file.dirname.mkpath
brew_file.make_relative_symlink original_brew_file
original_target = brew_file.readlink
original_directory_mode = brew_file.dirname.stat.mode & 07777
formula = formula("replace-brew-postinstall") do
T.bind(self, T.class_of(Formula))
url "foo-1.0"
end
installer = described_class.new(formula)
sandbox = instance_double(Sandbox).as_null_object
allow(installer).to receive_messages(post_install_formula_path: formula.path, use_sandbox?: true)
allow(formula).to receive_messages(logs: mktmpdir, network_access_allowed?: true)
allow(Sandbox).to receive_messages(full_write_isolation?: false, new: sandbox)
allow(sandbox).to receive(:run) do
FileUtils.rm_f brew_file
brew_file.write "malicious\n"
brew_file.dirname.chmod 0500
end
installer.post_install
expect(brew_file).to be_a_symlink
expect(brew_file.readlink).to eq(original_target)
expect(brew_file.dirname.stat.mode & 07777).to eq(original_directory_mode)
end
end
describe "#post_install_formula_path" do
it "uses the API formula for structured-only post-installs" do
formula = formula("api-install-steps") do
T.bind(self, T.class_of(Formula))
url "foo-1.0"
end
installer = described_class.new(formula)
allow(formula).to receive_messages(any_installed_prefix: mktmpdir, loaded_from_api?: true,
post_install_defined?: false)
expect(installer.post_install_formula_path).to eq(formula.full_name)
end
it "uses the keg formula for API post-installs with Ruby hooks" do
formula = formula("api-post-install-hook") do
T.bind(self, T.class_of(Formula))
url "foo-1.0"
end
installer = described_class.new(formula)
installed_prefix = mktmpdir
allow(formula).to receive_messages(any_installed_prefix: installed_prefix, loaded_from_api?: true,
post_install_defined?: true)
expect(installer.post_install_formula_path).to eq(installed_prefix/".brew/api-post-install-hook.rb")
end
end
describe "#pour" do
let(:f) do
formula("missing-bottle-tab") do
T.bind(self, T.class_of(Formula))
url "https://brew.sh/missing-bottle-tab-1.0.tar.gz"
bottle do
sha256 cellar: :any_skip_relocation,
Utils::Bottles.tag.to_sym => "d7b9f4e8bf83608b71fe958a99f19f2e5e68bb2582965d32e41759c24f1aef97"
end
end
end
let(:installer) { Class.new(described_class).new(f) }
let(:downloader) { instance_double(AbstractDownloadStrategy, basename: "missing-bottle-tab", stage: nil) }
let(:downloadable) { instance_double(Resource, downloader:) }
let(:tab) do
instance_double(Tab, changed_files: nil, source: { "versions" => {} }, write: nil).as_null_object
end
let(:keg) { instance_double(Keg) }
before do
allow(installer).to receive(:downloadable).and_return(downloadable)
allow(Utils::Bottles).to receive(:load_tab).with(f).and_return(tab)
allow(Tab).to receive(:clear_cache)
allow(Keg).to receive(:new).with(f.prefix).and_return(keg)
allow(keg).to receive(:replace_placeholders_with_locations)
allow(f.bottle_specification).to receive(:skip_relocation?).with(tab:).and_return(true)
end
it "preserves the skip-linkage decision for the default bottle domain" do
expect(keg).to receive(:replace_placeholders_with_locations).with(nil, skip_linkage: true)
installer.pour
end
it "relocates dynamic linkage without metadata from a bottle mirror" do
ENV["HOMEBREW_BOTTLE_DOMAIN"] = "https://mirror.example.com"
expect(keg).to receive(:replace_placeholders_with_locations).with(nil, skip_linkage: false)
installer.pour
end
it "explains how a bottle mirror can provide metadata once per invocation" do
ENV["HOMEBREW_BOTTLE_DOMAIN"] = "https://mirror.example.com"
expect { 2.times { installer.pour } }
.to output(satisfy do |stderr|
stderr.scan("OCI registry proxy").one? &&
stderr.include?("sh.brew.tab") && stderr.include?("HOMEBREW_ARTIFACT_DOMAIN")
end).to_stderr
end
end
describe "#build_bottle_postinstall" do
let(:f) do
formula "bottle-config" do
T.bind(self, T.class_of(Formula))
url "foo-1.0"
end
end
let(:config_file) { HOMEBREW_PREFIX/"etc/bottle-config.conf" }
before do
FileUtils.rm_rf f.rack
FileUtils.rm_f config_file
FileUtils.rm_f Pathname("#{config_file}.default")
end
after do
FileUtils.rm_rf f.rack
FileUtils.rm_f config_file
FileUtils.rm_f Pathname("#{config_file}.default")
end
it "stores new prefix config where install_etc_var restores it from" do
installer = described_class.new(f)
installer.build_bottle_preinstall
config_file.dirname.mkpath
config_file.write "new\n"
installer.build_bottle_postinstall
expect((f.bottle_prefix/"etc/bottle-config.conf").read).to eq("new\n")
end
end
describe "#verify_deps_exist" do
it "does not install an untapped dependency tap" do
formula = Testball.new
installer = described_class.new(formula)
tap = instance_double(Tap, user: "user", repository: "repo", to_s: "user/repo", installed?: false)
allow(installer).to receive(:compute_dependencies).and_raise(TapFormulaUnavailableError.new(tap, "foo"))
expect(tap).not_to receive(:ensure_installed!)
expect { installer.verify_deps_exist }
.to raise_error(TapFormulaUnavailableError, /If you trust this tap/) { |error|
expect(error.dependent).to eq(formula.full_name)
}
end
end
describe "#fetch_bottle_tab" do
it "does not enqueue cached bottle manifests" do
formula = formula("deno") do
T.bind(self, T.class_of(Formula))
url "https://brew.sh/deno-2.7.11.tar.gz"
bottle do
root_url HOMEBREW_BOTTLE_DEFAULT_DOMAIN
sha256 cellar: :any_skip_relocation,
Utils::Bottles.tag.to_sym => "d7b9f4e8bf83608b71fe958a99f19f2e5e68bb2582965d32e41759c24f1aef97"
end
end
installer = described_class.new(formula)
installer.download_queue = instance_double(Homebrew::DownloadQueue)
manifest_resource = formula.bottle&.github_packages_manifest_resource
cached_download = manifest_resource&.cached_download
allow(manifest_resource).to receive(:downloaded?).and_return(true)
expect(manifest_resource).to receive(:verify_download_integrity).with(cached_download) do
expect(Context.current.quiet?).to be(true)
end
expect(manifest_resource).not_to receive(:clear_cache)
expect(installer.download_queue).not_to receive(:enqueue)
installer.fetch_bottle_tab(enqueue: true)
end
it "enqueues invalid cached bottle manifests" do
formula = formula("deno") do
T.bind(self, T.class_of(Formula))
url "https://brew.sh/deno-2.7.11.tar.gz"
bottle do
root_url HOMEBREW_BOTTLE_DEFAULT_DOMAIN
sha256 cellar: :any_skip_relocation,
Utils::Bottles.tag.to_sym => "d7b9f4e8bf83608b71fe958a99f19f2e5e68bb2582965d32e41759c24f1aef97"
end
end
installer = described_class.new(formula)
installer.download_queue = instance_double(Homebrew::DownloadQueue)
manifest_resource = formula.bottle&.github_packages_manifest_resource
allow(manifest_resource).to receive(:downloaded?).and_return(true)
manifest_resource&.manifest_annotations = {}
expect(manifest_resource).to receive(:verify_download_integrity) do
expect(Context.current.quiet?).to be(true)
raise Resource::BottleManifest::Error
end
expect(installer.download_queue).to receive(:enqueue).with(manifest_resource)
installer.fetch_bottle_tab(enqueue: true)
# Read the raw ivar: the private memoising reader would re-parse the manifest.
# rubocop:disable Homebrew/NoInstanceVariableAccessInTests
expect(manifest_resource&.instance_variable_get(:@manifest_annotations)).to be_nil
# rubocop:enable Homebrew/NoInstanceVariableAccessInTests
end
end
describe "#enqueue_fetch" do
let(:formula) { TestballBottle.new }
let(:installer) { described_class.new(formula) }
let(:download_queue) { instance_double(Homebrew::DownloadQueue) }
let(:bottle) { instance_double(Bottle, cached_download: HOMEBREW_CACHE/"downloads/testball-bottle") }
before do
installer.download_queue = download_queue
allow(Homebrew::EnvConfig).to receive(:verify_attestations?).and_return(false)
allow(installer).to receive(:previously_fetched_formula)
allow(installer).to receive(:pour_bottle?).with(output_warning: true).and_return(true)
allow(installer).to receive(:downloadable).and_return(bottle)
allow(installer).to receive(:fetch_bottle_tab)
end
it "starts a bottle download before enqueueing dependencies after the prelude" do
installer.ran_prelude = true
expect(download_queue).to receive(:enqueue)
.with(bottle, check_attestation: false, stage: false).ordered
expect(installer).to receive(:fetch_dependencies).ordered
expect(download_queue).to receive(:enqueue)
.with(bottle, check_attestation: false).ordered
installer.enqueue_fetch
end
it "resolves dependencies before enqueueing a bottle without the prelude" do
expect(installer).to receive(:fetch_dependencies).ordered
expect(installer).to receive(:fetch_bottle_tab).with(enqueue: true).ordered
expect(download_queue).to receive(:enqueue)
.with(bottle, check_attestation: false).ordered
installer.enqueue_fetch
end
it "does not requeue a bottle already enqueued by the prelude fetch" do
allow(installer).to receive(:pour_bottle?).and_return(true)
expect(download_queue).to receive(:enqueue)
.with(bottle, check_attestation: false, stage: true).once
installer.prelude_fetch
expect(installer).to receive(:fetch_dependencies)
installer.enqueue_fetch
end
end
describe "linking defaults" do
it "links non-keg-only formulae when link_keg is false" do
ordinary_formula = formula "homebrew-link-default" do
T.bind(self, T.class_of(Formula))
url "foo-1.0"
end
expect(described_class.new(ordinary_formula, link_keg: false).link_keg).to be true
end
it "links non-keg-only dependencies even when they were not previously linked" do
dependency_formula = formula "homebrew-link-default-dependency" do
T.bind(self, T.class_of(Formula))
url "foo-1.0"
end
dependency = instance_double(Dependency, to_formula: dependency_formula, name: dependency_formula.name,
options: Options.new)
installer = described_class.new(Testball.new)
# Sorbet doesn't like `T.let(nil, T.nilable(described_class))`, but the
# RuboCop will always autocorrect to that.
# rubocop:disable RSpec/DescribedClass
child_installer = T.let(nil, T.nilable(FormulaInstaller))
# rubocop:enable RSpec/DescribedClass
allow(dependency_formula).to receive_messages(
linked_keg: Pathname("/tmp/nonexistent-linked-keg"),
latest_version_installed?: false,
tap: nil,
any_version_installed?: false,
)
allow(installer).to receive(:oh1)
allow(described_class).to receive(:new).and_wrap_original do |original, formula, **kwargs|
instance = original.call(formula, **kwargs)
next instance if formula != dependency_formula
child_installer = instance
allow(instance).to receive_messages(prelude: true, install: true, finish: true)
instance
end
installer.install_dependency(dependency)
expect(child_installer).not_to be_installed_on_request
expect(child_installer&.link_keg).to be true
end
end
describe "#check_conflicts" do
let(:test_formula) do
formula "testball" do
T.bind(self, T.class_of(Formula))
url "https://brew.sh/testball-0.1.tar.gz"
conflicts_with "other"
end
end
let(:conflicting_formula) do
formula "other" do
T.bind(self, T.class_of(Formula))
url "https://brew.sh/other-0.1.tar.gz"
conflicts_with "testball"
end
end
before { allow(Formulary).to receive(:factory).with("other").and_return(conflicting_formula) }
context "when conflicting formula is installed but not linked" do
before do
linked_keg = instance_double(Pathname, exist?: false)
opt_prefix = instance_double(Pathname, exist?: true)
allow(conflicting_formula).to receive_messages(linked_keg:, opt_prefix:)
end
it "does not raise an error" do
installer = described_class.new(test_formula, link_keg: true)
expect { installer.check_conflicts }.not_to raise_error
end
end
context "when conflicting formula is installed" do
before do
linked_keg = opt_prefix = instance_double(Pathname, exist?: true)
allow(conflicting_formula).to receive_messages(linked_keg:, opt_prefix:)
end
it "raises an error if linking keg" do
installer = described_class.new(test_formula, link_keg: true)
expect { installer.check_conflicts }.to raise_error(FormulaConflictError)
end
it "does not raise an error with force set" do
installer = described_class.new(test_formula, link_keg: true, force: true)
expect { installer.check_conflicts }.not_to raise_error
end
it "does not raise an error with skip_link set" do
installer = described_class.new(test_formula, link_keg: true, skip_link: true)
expect { installer.check_conflicts }.not_to raise_error
end
it "does not raise an error if not linking keg" do
allow(test_formula).to receive(:keg_only?).and_return(true)
installer = described_class.new(test_formula, link_keg: false, installed_on_request: false)
expect { installer.check_conflicts }.not_to raise_error
end
end
it "ignores conflicts that name the formula being installed" do
f = formula("terraform", tap: Tap.fetch("thirdparty", "selfconflict")) do
T.bind(self, T.class_of(Formula))
url "foo-1.0"
conflicts_with "terraform"
end
expect(Formulary).not_to receive(:factory)
described_class.new(f).check_conflicts
ensure
FileUtils.rm_rf HOMEBREW_TAP_DIRECTORY/"thirdparty"
end
end
describe "#install_dependencies" do
it "marks only outdated dependencies as upgradable in the header" do
outdated = formula "outdated-dependency" do
T.bind(self, T.class_of(Formula))
url "foo-1.0"
end
uninstalled = formula "uninstalled-dependency" do
T.bind(self, T.class_of(Formula))
url "foo-1.0"
end
allow(outdated).to receive_messages(any_version_installed?: true, outdated?: true)
allow(uninstalled).to receive_messages(any_version_installed?: false, outdated?: false)
deps = [
instance_double(Dependency, to_formula: outdated, name: outdated.name, to_s: outdated.name),
instance_double(Dependency, to_formula: uninstalled, name: uninstalled.name, to_s: uninstalled.name),
]
installer = described_class.new(Testball.new)
allow(installer).to receive(:install_dependency)
allow_any_instance_of(StringIO).to receive(:tty?).and_return(true)
allow(Homebrew::EnvConfig).to receive(:no_emoji?).and_return(true)
expect { installer.install_dependencies(deps) }
.to output(/outdated-dependency.*\(upgradable\).*and.*uninstalled-dependency[^(]*$/m).to_stdout
end
it "does not render the first dependency name bolder than the rest" do
ENV["HOMEBREW_COLOR"] = "1"
dep_a = formula("dep-a") do
T.bind(self, T.class_of(Formula))
url "foo-1.0"
end
dep_b = formula("dep-b") do
T.bind(self, T.class_of(Formula))
url "foo-1.0"
end
[dep_a, dep_b].each { |f| allow(f).to receive_messages(any_version_installed?: true, outdated?: true) }
deps = [
instance_double(Dependency, to_formula: dep_a, name: dep_a.name, to_s: dep_a.name),
instance_double(Dependency, to_formula: dep_b, name: dep_b.name, to_s: dep_b.name),
]
installer = described_class.new(Testball.new)
allow(installer).to receive(:install_dependency)
allow_any_instance_of(StringIO).to receive(:tty?).and_return(true)
expect { installer.install_dependencies(deps) }
.to output(/:\e\[0m /).to_stdout
end
it "shows the formula header after installing a single dependency" do
dep = formula("single-dep") do
T.bind(self, T.class_of(Formula))
url "foo-1.0"
end
deps = [instance_double(Dependency, to_formula: dep, name: dep.name, to_s: dep.name)]
installer = described_class.new(Testball.new)
allow(installer).to receive(:install_dependency)
installer.install_dependencies(deps)
expect(installer.show_header?).to be(true)
end
end
describe "#expand_dependencies_for_formula" do
it "checks equal dependency satisfaction once per expansion" do
shared_formula = instance_double(Formula, deps: [], name: "shared", full_name: "shared")
shared_dep = Dependency.new("shared")
repeated_shared_dep = Dependency.new("shared")
expect(shared_dep).to receive(:satisfied?).once.and_return(false)
expect(repeated_shared_dep).not_to receive(:satisfied?)
allow(shared_dep).to receive(:to_formula).and_return(shared_formula)
allow(repeated_shared_dep).to receive(:to_formula).and_return(shared_formula)
first_parent = Dependency.new("first-parent")
first_parent_formula = instance_double(Formula, deps: [shared_dep], name: "first-parent",
full_name: "first-parent")
allow(first_parent).to receive_messages(satisfied?: false, to_formula: first_parent_formula)
second_parent = Dependency.new("second-parent")
second_parent_formula = instance_double(Formula, deps: [repeated_shared_dep], name: "second-parent",
full_name: "second-parent")
allow(second_parent).to receive_messages(satisfied?: false, to_formula: second_parent_formula)
f = formula "homebrew-expand-dependencies-cache" do
T.bind(self, T.class_of(Formula))
url "foo-1.0"
end
allow(f).to receive(:deps).and_return([first_parent, second_parent])
installer = described_class.new(f)
build_options = BuildOptions.new(Options.new, Options.new)
allow(installer).to receive_messages(effective_build_options_for: build_options, install_bottle_for?: false)
deps = installer.expand_dependencies_for_formula(f)
expect(deps.map(&:name)).to eq(%w[shared first-parent second-parent])
end
it "checks uses_from_macos dependencies with different bounds separately" do
shared_formula = instance_double(Formula, deps: [], name: "shared", full_name: "shared")
first_dep = UsesFromMacOSDependency.new("shared", [], bounds: { since: :ventura })
second_dep = UsesFromMacOSDependency.new("shared", [], bounds: { since: :sonoma })
expect(first_dep).to receive(:satisfied?).once.and_return(false)
expect(second_dep).to receive(:satisfied?).once.and_return(false)
allow(first_dep).to receive(:to_formula).and_return(shared_formula)
allow(second_dep).to receive(:to_formula).and_return(shared_formula)
first_parent = Dependency.new("first-parent")
first_parent_formula = instance_double(Formula, deps: [first_dep], name: "first-parent",
full_name: "first-parent")
allow(first_parent).to receive_messages(satisfied?: false, to_formula: first_parent_formula)
second_parent = Dependency.new("second-parent")
second_parent_formula = instance_double(Formula, deps: [second_dep], name: "second-parent",
full_name: "second-parent")
allow(second_parent).to receive_messages(satisfied?: false, to_formula: second_parent_formula)
f = formula "homebrew-expand-uses-from-macos-dependencies-cache" do
T.bind(self, T.class_of(Formula))
url "foo-1.0"
end
allow(f).to receive(:deps).and_return([first_parent, second_parent])
installer = described_class.new(f)
build_options = BuildOptions.new(Options.new, Options.new)
allow(installer).to receive_messages(effective_build_options_for: build_options, install_bottle_for?: false)
deps = installer.expand_dependencies_for_formula(f)
expect(deps.map(&:name)).to eq(%w[shared first-parent second-parent])
end
end
describe "versioned keg-only linking defaults" do
let(:base_name) { "homebrew-versioned-formula" }
let(:formula_name) { "#{base_name}@1.0" }
let(:keg_only_formula) do
formula formula_name do
T.bind(self, T.class_of(Formula))
url "foo-1.0"
keg_only :versioned_formula
end
end
before do
allow(keg_only_formula).to receive_messages(any_version_installed?: false,
link_overwrite_formulae: [])
end
it "does not link by default when it is not installed on request" do
fi = described_class.new(keg_only_formula)
expect(fi.link_keg).to be false
end
it "links by default when no sibling variants are installed" do
fi = described_class.new(keg_only_formula, installed_on_request: true)
expect(fi.link_keg).to be true
end
it "does not link by default when any version is already installed" do
allow(keg_only_formula).to receive(:any_version_installed?).and_return(true)
fi = described_class.new(keg_only_formula)
expect(fi.link_keg).to be false
end
it "links when explicitly requested" do
allow(keg_only_formula).to receive(:any_version_installed?).and_return(true)
fi = described_class.new(keg_only_formula, link_keg: true)
expect(fi.link_keg).to be true
end
it "does not link by default when another @-versioned formula is installed" do
other_version = formula "#{base_name}@2.0" do
T.bind(self, T.class_of(Formula))
url "foo-2.0"
keg_only :versioned_formula
end
allow(other_version).to receive(:any_version_installed?).and_return(true)
allow(keg_only_formula).to receive(:link_overwrite_formulae).and_return([other_version])
fi = described_class.new(keg_only_formula)
expect(fi.link_keg).to be false
end
it "does not link by default when the unversioned sibling is installed" do
unversioned_formula = formula base_name do
T.bind(self, T.class_of(Formula))
url "foo-1.0"
end
allow(unversioned_formula).to receive(:any_version_installed?).and_return(true)
allow(keg_only_formula).to receive(:link_overwrite_formulae).and_return([unversioned_formula])
fi = described_class.new(keg_only_formula)
expect(fi.link_keg).to be false
end
it "does not link by default when the unversioned sibling is keg-only" do
unversioned_formula = formula base_name do
T.bind(self, T.class_of(Formula))
url "foo-1.0"
keg_only "some reason"
end
allow(keg_only_formula).to receive(:link_overwrite_formulae).and_return([unversioned_formula])
fi = described_class.new(keg_only_formula)
expect(fi.link_keg).to be false
end
it "does not link by default when the -full variant is installed" do
full_variant = formula "#{base_name}-full" do
T.bind(self, T.class_of(Formula))
url "foo-full-1.0"
keg_only :versioned_formula
end
allow(full_variant).to receive(:any_version_installed?).and_return(true)
allow(keg_only_formula).to receive(:link_overwrite_formulae).and_return([full_variant])
fi = described_class.new(keg_only_formula)
expect(fi.link_keg).to be false
end
it "does not link by default when the non-full variant is installed" do
full_formula = formula "#{base_name}-full" do
T.bind(self, T.class_of(Formula))
url "foo-full-1.0"
keg_only :versioned_formula
end
non_full_variant = formula base_name do
T.bind(self, T.class_of(Formula))
url "foo-1.0"
keg_only :versioned_formula
end
allow(non_full_variant).to receive(:any_version_installed?).and_return(true)
allow(full_formula).to receive_messages(any_version_installed?: false,
link_overwrite_formulae: [non_full_variant])
fi = described_class.new(full_formula)
expect(fi.link_keg).to be false
end
end
describe "#link" do
let(:versioned_formula) do
formula "homebrew-versioned-formula@1.0" do
T.bind(self, T.class_of(Formula))
url "foo-1.0"
keg_only :versioned_formula
end
end
let(:other_version) do
formula "homebrew-versioned-formula" do
T.bind(self, T.class_of(Formula))
url "foo-2.0"
keg_only :versioned_formula
end
end
let(:keg) do
instance_double(Keg, linked?: false)
end
before do
allow(Formula).to receive(:clear_cache)
allow(Cask::Caskroom).to receive(:path).and_return(Pathname("/tmp/nonexistent-caskroom"))
allow(versioned_formula).to receive_messages(link_overwrite_formulae: [other_version],
any_version_installed?: false)
allow(other_version).to receive(:any_version_installed?).and_return(true)
end
it "only optlinks when default linking is disabled by an installed sibling" do
installer = described_class.new(versioned_formula)
expect(installer.link_keg).to be false
expect(Homebrew::Unlink).not_to receive(:unlink_link_overwrite_formulae)
expect(keg).to receive(:optlink).with(verbose: false, overwrite: false)
expect(keg).not_to receive(:link)
installer.link(keg)
end
it "unlinks siblings before linking when explicitly requested" do
installer = described_class.new(versioned_formula, link_keg: true)
expect(installer.link_keg).to be true
expect(Homebrew::Unlink).to receive(:unlink_link_overwrite_formulae).with(versioned_formula,
verbose: false).ordered
expect(keg).to receive(:link).with(verbose: false, overwrite: false).ordered
installer.link(keg)
end
end
describe "#link_manual_command_warning" do
let(:base_name) { "homebrew-versioned-formula" }
let(:formula_name) { "#{base_name}@1.0" }
let(:keg_only_formula) do
formula formula_name do
T.bind(self, T.class_of(Formula))
url "foo-1.0"
keg_only :versioned_formula
end
end
it "explains why a versioned formula was installed but not linked" do
unversioned_formula = formula base_name do
T.bind(self, T.class_of(Formula))
url "foo-1.0"
end
allow(unversioned_formula).to receive_messages(any_version_installed?: true, linked?: true)
allow(keg_only_formula).to receive_messages(any_version_installed?: false, linked?: false,
link_overwrite_formulae: [unversioned_formula])
installer = described_class.new(keg_only_formula, installed_on_request: true)
expect(installer.link_manual_command_warning).to eq <<~EOS
#{formula_name} was installed but not linked because #{base_name} is already linked.
To link this version, run:
brew link #{formula_name}
EOS
end
end
describe "#check_install_sanity" do
it "raises on direct cyclic dependency" do
ENV["HOMEBREW_DEVELOPER"] = "1"
dep_name = "homebrew-test-cyclic"
dep_path = CoreTap.instance.new_formula_path(dep_name)
dep_path.write <<~RUBY
class #{Formulary.class_s(dep_name)} < Formula
url "foo"
version "0.1"
depends_on "#{dep_name}"
end
RUBY
f = Formulary.factory(dep_name)
fi = described_class.new(f)
expect do
fi.check_install_sanity
end.to raise_error(CannotInstallFormulaError)
end
it "raises on indirect cyclic dependency" do
ENV["HOMEBREW_DEVELOPER"] = "1"
formula1_name = "homebrew-test-formula1"
formula2_name = "homebrew-test-formula2"
formula1_path = CoreTap.instance.new_formula_path(formula1_name)
formula1_path.write <<~RUBY
class #{Formulary.class_s(formula1_name)} < Formula
url "foo"
version "0.1"
depends_on "#{formula2_name}"
end
RUBY
formula1 = Formulary.factory(formula1_name)
formula2_path = CoreTap.instance.new_formula_path(formula2_name)
formula2_path.write <<~RUBY
class #{Formulary.class_s(formula2_name)} < Formula
url "foo"
version "0.1"
depends_on "#{formula1_name}"
end
RUBY
fi = described_class.new(formula1)
expect do
fi.check_install_sanity
end.to raise_error(CannotInstallFormulaError)
end
it "raises on pinned dependency" do
dep_name = "homebrew-test-dependency"
dep_path = CoreTap.instance.new_formula_path(dep_name)
dep_path.write <<~RUBY
class #{Formulary.class_s(dep_name)} < Formula
url "foo"
version "0.2"
end
RUBY
dependency = Formulary.factory(dep_name)
dependent = formula do
T.bind(self, T.class_of(Formula))
url "foo"
version "0.5"
depends_on dependency.name.to_s
end
(dependency.prefix("0.1")/"bin"/"a").mkpath
HOMEBREW_PINNED_KEGS.mkpath
FileUtils.ln_s dependency.prefix("0.1"), HOMEBREW_PINNED_KEGS/dep_name
dependency_keg = Keg.new(dependency.prefix("0.1"))
dependency_keg.link
expect(dependency_keg).to be_linked
expect(dependency).to be_pinned
fi = described_class.new(dependent)
expect do
fi.check_install_sanity
end.to raise_error(CannotInstallFormulaError)
end
end
describe "#forbidden_license_check" do
it "raises on forbidden license on formula" do
ENV["HOMEBREW_FORBIDDEN_LICENSES"] = "AGPL-3.0"
f_name = "homebrew-forbidden-license"
f_path = CoreTap.instance.new_formula_path(f_name)
f_path.write <<~RUBY
class #{Formulary.class_s(f_name)} < Formula
url "foo"
version "0.1"
license "AGPL-3.0"
end
RUBY
f = Formulary.factory(f_name)
fi = described_class.new(f)
expect do
fi.forbidden_license_check
end.to raise_error(CannotInstallFormulaError, /#{f_name}'s licenses are all forbidden/)
end
it "raises on forbidden license on formula with contact instructions" do
ENV["HOMEBREW_FORBIDDEN_LICENSES"] = "AGPL-3.0"
ENV["HOMEBREW_FORBIDDEN_OWNER"] = owner = "your dog"
ENV["HOMEBREW_FORBIDDEN_OWNER_CONTACT"] = contact = "Woof loudly to get this unblocked."
f_name = "homebrew-forbidden-license"
f_path = CoreTap.instance.new_formula_path(f_name)
f_path.write <<~RUBY
class #{Formulary.class_s(f_name)} < Formula
url "foo"
version "0.1"
license "AGPL-3.0"
end
RUBY
f = Formulary.factory(f_name)
fi = described_class.new(f)
expect do
fi.forbidden_license_check
end.to raise_error(CannotInstallFormulaError, /#{owner}.+\n#{contact}/m)
end
it "raises on forbidden license on dependency" do
ENV["HOMEBREW_FORBIDDEN_LICENSES"] = "GPL-3.0"
dep_name = "homebrew-forbidden-dependency-license"
dep_path = CoreTap.instance.new_formula_path(dep_name)
dep_path.write <<~RUBY
class #{Formulary.class_s(dep_name)} < Formula
url "foo"
version "0.1"
license "GPL-3.0"
end
RUBY
f_name = "homebrew-forbidden-dependent-license"
f_path = CoreTap.instance.new_formula_path(f_name)
f_path.write <<~RUBY
class #{Formulary.class_s(f_name)} < Formula
url "foo"
version "0.1"
depends_on "#{dep_name}"
end
RUBY
f = Formulary.factory(f_name)
fi = described_class.new(f)
expect do
fi.forbidden_license_check
end.to raise_error(CannotInstallFormulaError, /dependency on #{dep_name} where all/)
end
it "raises on forbidden symbol license on formula" do
ENV["HOMEBREW_FORBIDDEN_LICENSES"] = "public_domain"
f_name = "homebrew-forbidden-license"
f_path = CoreTap.instance.new_formula_path(f_name)
f_path.write <<~RUBY
class #{Formulary.class_s(f_name)} < Formula
url "foo"
version "0.1"
license :public_domain
end
RUBY
f = Formulary.factory(f_name)
fi = described_class.new(f)
expect do
fi.forbidden_license_check
end.to raise_error(CannotInstallFormulaError, /#{f_name}'s licenses are all forbidden/)
end
end
describe "#forbidden_tap_check" do
before do
allow(Tap).to receive_messages(allowed_taps: allowed_taps_set, forbidden_taps: forbidden_taps_set)
allow(Homebrew::Trust).to receive(:trusted_tap?).and_return(true)
end
let(:homebrew_forbidden) { Tap.fetch("homebrew/forbidden") }
let(:allowed_third_party) { Tap.fetch("nothomebrew/allowed") }
let(:disallowed_third_party) { Tap.fetch("nothomebrew/notallowed") }
let(:allowed_taps_set) { [allowed_third_party.name] }
let(:forbidden_taps_set) { [homebrew_forbidden.name] }
it "raises on forbidden tap on formula" do
f_tap = homebrew_forbidden
f_name = "homebrew-forbidden-tap"
f_path = homebrew_forbidden.new_formula_path(f_name)
f_path.parent.mkpath
f_path.write <<~RUBY
class #{Formulary.class_s(f_name)} < Formula
url "foo"
version "0.1"
end
RUBY
f = Formulary.factory("#{f_tap}/#{f_name}")
fi = described_class.new(f)
expect do
fi.forbidden_tap_check
end.to raise_error(CannotInstallFormulaError, /has the tap #{f_tap}/)
ensure
FileUtils.rm_r(f_path.parent.parent)
end
it "raises on not allowed third-party tap on formula" do
f_tap = disallowed_third_party
f_name = "homebrew-not-allowed-tap"
f_path = disallowed_third_party.new_formula_path(f_name)
f_path.parent.mkpath
f_path.write <<~RUBY
class #{Formulary.class_s(f_name)} < Formula
url "foo"
version "0.1"
end
RUBY
f = Formulary.factory("#{f_tap}/#{f_name}")
fi = described_class.new(f)
expect do
fi.forbidden_tap_check
end.to raise_error(CannotInstallFormulaError, /has the tap #{f_tap}/)
ensure
FileUtils.rm_r(f_path.parent.parent.parent)
end
it "does not raise on allowed tap on formula" do
f_tap = allowed_third_party
f_name = "homebrew-allowed-tap"
f_path = allowed_third_party.new_formula_path(f_name)
f_path.parent.mkpath
f_path.write <<~RUBY
class #{Formulary.class_s(f_name)} < Formula
url "foo"
version "0.1"
end
RUBY
f = Formulary.factory("#{f_tap}/#{f_name}")
fi = described_class.new(f)
expect { fi.forbidden_tap_check }.not_to raise_error
ensure
FileUtils.rm_r(f_path.parent.parent.parent)
end
it "raises on forbidden tap on dependency" do
dep_tap = homebrew_forbidden
dep_name = "homebrew-forbidden-dependency-tap"
dep_path = homebrew_forbidden.new_formula_path(dep_name)
dep_path.parent.mkpath
dep_path.write <<~RUBY
class #{Formulary.class_s(dep_name)} < Formula
url "foo"
version "0.1"
end
RUBY
f_name = "homebrew-forbidden-dependent-tap"
f_path = CoreTap.instance.new_formula_path(f_name)
f_path.write <<~RUBY
class #{Formulary.class_s(f_name)} < Formula
url "foo"
version "0.1"
depends_on "#{dep_name}"
end
RUBY
f = Formulary.factory(f_name)
fi = described_class.new(f)
expect do
fi.forbidden_tap_check
end.to raise_error(CannotInstallFormulaError, /from the #{dep_tap} tap but/)
ensure
FileUtils.rm_r(dep_path.parent.parent)
end
end
describe "#forbidden_formula_check" do
it "raises on forbidden formula" do
ENV["HOMEBREW_FORBIDDEN_FORMULAE"] = f_name = "homebrew-forbidden-formula"
f_path = CoreTap.instance.new_formula_path(f_name)
f_path.write <<~RUBY
class #{Formulary.class_s(f_name)} < Formula
url "foo"
version "0.1"
end
RUBY
f = Formulary.factory(f_name)
fi = described_class.new(f)
expect do
fi.forbidden_formula_check
end.to raise_error(CannotInstallFormulaError, /#{f_name} was forbidden/)
end
it "raises on forbidden dependency" do
ENV["HOMEBREW_FORBIDDEN_FORMULAE"] = dep_name = "homebrew-forbidden-dependency-formula"
dep_path = CoreTap.instance.new_formula_path(dep_name)
dep_path.write <<~RUBY
class #{Formulary.class_s(dep_name)} < Formula
url "foo"
version "0.1"
end
RUBY
f_name = "homebrew-forbidden-dependent-formula"
f_path = CoreTap.instance.new_formula_path(f_name)
f_path.write <<~RUBY
class #{Formulary.class_s(f_name)} < Formula
url "foo"
version "0.1"
depends_on "#{dep_name}"
end
RUBY
f = Formulary.factory(f_name)
fi = described_class.new(f)
expect do
fi.forbidden_formula_check
end.to raise_error(CannotInstallFormulaError, /#{dep_name} formula was forbidden/)
end
end
describe "#prelude_fetch" do
context "with an API-loaded bottled formula" do
let(:deno_formula) do
formula("deno") do
T.bind(self, T.class_of(Formula))
url "https://brew.sh/deno-2.7.11.tar.gz"
end
end
let(:formula_struct) do
Homebrew::API::FormulaStruct.new(
bottle_checksums: [
{
cellar: :any_skip_relocation,
Utils::Bottles.tag.to_sym => "d7b9f4e8bf83608b71fe958a99f19f2e5e68bb2582965d32e41759c24f1aef97",
},
],
bottle_present: true,
desc: "deno",
homepage: "https://brew.sh",
license: "MIT",
ruby_source_checksum: "abc123",
stable_present: true,
stable_version: "2.7.11",
)
end
let(:installer) do
installer = described_class.new(deno_formula, ignore_deps: true)
installer.download_queue = instance_double(Homebrew::DownloadQueue)
installer
end
before do
allow(deno_formula).to receive_messages(
bottle_tag?: true,
core_formula?: true,
loaded_from_internal_api?: true,
pour_bottle?: true,
)
allow(Homebrew::API::Internal).to receive(:formula_struct).with("deno").and_return(formula_struct)
end
it "uses API bottle metadata to enqueue the manifest and bottle" do
expect(deno_formula).not_to receive(:bottle_for_tag)
expect(deno_formula).not_to receive(:bottle)
expect(installer.download_queue).to receive(:enqueue).with(an_instance_of(Resource::BottleManifest))
expect(installer.download_queue).to receive(:enqueue)
.with(an_instance_of(Bottle), check_attestation: false, stage: true)
installer.prelude_fetch
end
it "enqueues only the bottle manifest when fetching metadata" do
expect(installer.download_queue).to receive(:enqueue).with(an_instance_of(Resource::BottleManifest))
installer.prelude_fetch(metadata_only: true)
end
it "enqueues the bottle without repeating metadata work after a metadata-only run" do
expect(installer.download_queue).to receive(:enqueue).with(an_instance_of(Resource::BottleManifest)).once
installer.prelude_fetch(metadata_only: true)
expect(installer.download_queue).to receive(:enqueue)
.with(an_instance_of(Bottle), check_attestation: false, stage: true)
installer.prelude_fetch
end
end
it "does not repeat source download prelude work" do
f = formula("homebrew-prelude-fetch-once") do
T.bind(self, T.class_of(Formula))
url "https://brew.sh/prelude-fetch-once-0.1.tar.gz"
end
allow(f).to receive(:loaded_from_api?).and_return(true)
fi = described_class.new(f, ignore_deps: true)
fi.download_queue = instance_double(Homebrew::DownloadQueue)
expect(Homebrew::API::Formula).to receive(:source_download)
.with(f, download_queue: fi.download_queue, enqueue: true)
.once
fi.prelude_fetch
fi.prelude_fetch
end
it "raises on forbidden formula tap before fetching the source from the API" do
homebrew_forbidden = Tap.fetch("homebrew/forbidden")
allow(Tap).to receive_messages(allowed_taps: [], forbidden_taps: [homebrew_forbidden.name])
f_name = "homebrew-forbidden-fail-fast-tap"
f_path = homebrew_forbidden.new_formula_path(f_name)
f_path.parent.mkpath
f_path.write <<~RUBY
class #{Formulary.class_s(f_name)} < Formula
url "foo"
version "0.1"
end
RUBY
f = Formulary.factory("#{homebrew_forbidden}/#{f_name}")
allow(f).to receive(:loaded_from_api?).and_return(true)
fi = described_class.new(f)
expect(Homebrew::API::Formula).not_to receive(:source_download)
expect { fi.prelude_fetch }.to raise_error(CannotInstallFormulaError, /has the tap #{homebrew_forbidden}/)
ensure
FileUtils.rm_r(f_path.parent.parent)
end
it "raises on forbidden formula before fetching the source from the API" do
ENV["HOMEBREW_FORBIDDEN_FORMULAE"] = f_name = "homebrew-forbidden-fail-fast-formula"
f_path = CoreTap.instance.new_formula_path(f_name)
f_path.write <<~RUBY
class #{Formulary.class_s(f_name)} < Formula
url "foo"
version "0.1"
end
RUBY
f = Formulary.factory(f_name)
allow(f).to receive(:loaded_from_api?).and_return(true)
fi = described_class.new(f)
expect(Homebrew::API::Formula).not_to receive(:source_download)
expect { fi.prelude_fetch }.to raise_error(CannotInstallFormulaError, /was forbidden/)
end
end
specify "install fails with BuildError when a system() call fails" do
ENV["HOMEBREW_TEST_NO_EXIT_CLEANUP"] = "1"
ENV["FAILBALL_BUILD_ERROR"] = "1"
expect do
temporary_install(Failball.new)
end.to raise_error(BuildError)
end
specify "install fails with a RuntimeError when #install raises" do
ENV["HOMEBREW_TEST_NO_EXIT_CLEANUP"] = "1"
expect do
temporary_install(Failball.new)
end.to raise_error(RuntimeError)
end
describe "#caveats" do
subject(:formula_installer) { described_class.new(Testball.new) }
it "shows audit problems if HOMEBREW_DEVELOPER is set" do
ENV["HOMEBREW_DEVELOPER"] = "1"
with_env(HOMEBREW_NO_INSTALL_FROM_API: "1") do
formula_installer.fetch
formula_installer.install
end
expect(formula_installer).to receive(:audit_installed).and_call_original
formula_installer.caveats
end
end
describe "#install_service" do
it "works if service is set" do
formula = Testball.new
service = Homebrew::Service.new(formula)
launchd_service_path = formula.launchd_service_path
service_path = formula.systemd_service_path
formula.opt_prefix.mkpath
expect(formula).to receive(:service?).and_return(true)
expect(formula).to receive(:service).at_least(:once).and_return(service)
expect(formula).to receive(:launchd_service_path).and_call_original
expect(formula).to receive(:systemd_service_path).and_call_original
expect(service).to receive(:timed?).and_return(false)
expect(service).to receive(:command?).and_return(true)
expect(service).to receive(:to_plist).and_return("plist")
expect(service).to receive(:to_systemd_unit).and_return("unit")
installer = described_class.new(formula)
expect do
installer.install_service
end.not_to output(/Error: Failed to install service files/).to_stderr
expect(launchd_service_path).to exist
expect(service_path).to exist
end
it "works if timed service is set" do
formula = Testball.new
service = Homebrew::Service.new(formula)
launchd_service_path = formula.launchd_service_path
service_path = formula.systemd_service_path
timer_path = formula.systemd_timer_path
formula.opt_prefix.mkpath
expect(formula).to receive(:service?).and_return(true)
expect(formula).to receive(:service).at_least(:once).and_return(service)
expect(formula).to receive(:launchd_service_path).and_call_original
expect(formula).to receive(:systemd_service_path).and_call_original
expect(formula).to receive(:systemd_timer_path).and_call_original
expect(service).to receive(:timed?).and_return(true)
expect(service).to receive(:command?).and_return(true)
expect(service).to receive(:to_plist).and_return("plist")
expect(service).to receive(:to_systemd_unit).and_return("unit")
expect(service).to receive(:to_systemd_timer).and_return("timer")
installer = described_class.new(formula)
expect do
installer.install_service
end.not_to output(/Error: Failed to install service files/).to_stderr
expect(launchd_service_path).to exist
expect(service_path).to exist
expect(timer_path).to exist
end
it "returns without definition" do
formula = Testball.new
path = formula.launchd_service_path
formula.opt_prefix.mkpath
expect(formula).to receive(:service?).and_return(nil)
expect(formula).not_to receive(:launchd_service_path)
installer = described_class.new(formula)
expect do
installer.install_service
end.not_to output(/Error: Failed to install service files/).to_stderr
expect(path).not_to exist
end
end
describe "#build" do
it "attempts source download when formula is loaded from API" do
formula = Testball.new
allow(formula).to receive(:loaded_from_api?).and_return(true)
source_formula = Testball.new
allow(source_formula).to receive(:loaded_from_api?).and_return(false)
expect(Homebrew::API::Formula).to receive(:source_download_formula)
.with(formula)
.and_return(source_formula)
installer = described_class.new(formula)
# Stub out the actual build subprocess since we only care about the guard
allow(installer).to receive(:build_argv).and_return([])
allow(Utils).to receive(:safe_fork)
allow(source_formula).to receive_messages(logs: mktmpdir, update_head_version: nil, prefix: mktmpdir,
network_access_allowed?: true)
allow(Keg).to receive(:new).and_return(instance_double(Keg, empty_installation?: false))
installer.build
expect(installer.formula).to eq(source_formula)
end
it "raises when formula is loaded from API and source download fails" do
formula = Testball.new
allow(formula).to receive(:loaded_from_api?).and_return(true)
expect(Homebrew::API::Formula).to receive(:source_download_formula)
.with(formula)
.and_raise(CannotInstallFormulaError, "source code not found")
installer = described_class.new(formula)
expect do
installer.build
end.to raise_error(CannotInstallFormulaError, /source code not found/)
end
it "exposes local formula and trust paths to the sandbox" do
formula_path = mktmpdir/"homebrew-local-formula.rb"
FileUtils.touch formula_path
formula = formula("homebrew-local-formula", path: formula_path) do
T.bind(self, T.class_of(Formula))
url "foo"
version "1.0"
end
installer = described_class.new(formula)
sandbox = instance_double(Sandbox)
allow(installer).to receive(:build_argv).and_return([])
allow(Sandbox).to receive_messages(available?: true, new: sandbox)
allow(sandbox).to receive_messages(record_log: nil, allow_read_if_exists: nil, allow_write_temp_and_cache: nil,
allow_write_log: nil, allow_cvs: nil, allow_fossil: nil,
allow_write_xcode: nil, allow_write_cellar: nil, deny_read_home: nil,
run: nil)
allow(formula).to receive_messages(logs: mktmpdir, update_head_version: nil, prefix: mktmpdir,
network_access_allowed?: true)
allow(Keg).to receive(:new).and_return(instance_double(Keg, empty_installation?: false))
expect(sandbox).to receive(:allow_read_if_exists).with(path: formula_path).ordered
expect(sandbox).to receive(:allow_read_if_exists).with(path: Homebrew::Trust.trust_file).ordered
with_env(HOMEBREW_REQUIRE_TAP_TRUST: "1") do
installer.build
end
end
end
end