sorbet: Use T.bind in formula blocks in tests

- Don't duplicate all of `formula.rbi` in the `rspec.rbi` shim because
  it's a terrible pattern.
- I also noticed a supposedly typechecked file
  `test/test_bot/formulae_spec.rb` that was definitely cheating through
  the use of `T.bind(self, T.untyped)` and `T.unsafe`. So, follow the
  same `T.bind(self, T.class_of(Formula))` pattern in test files to make
  sure we're getting proper type checking in `formula` blocks.
This commit is contained in:
Issy Long
2026-06-04 23:40:59 +01:00
parent 5472a6bca8
commit 540457b88b
19 changed files with 95 additions and 49 deletions
@@ -44,14 +44,6 @@ class RSpec::Core::ExampleGroup
# These methods are mixed into specs via
# `config.include(Test::Helper::{Formula,Cask})` in `test/spec_helper.rb`;
# declare them here so Sorbet can resolve them in typed spec files.
sig {
params(
name: String, path: T.nilable(Pathname), spec: Symbol, alias_path: T.nilable(Pathname),
tap: T.nilable(Tap), block: T.nilable(T.proc.bind(Formula).void)
).returns(::Formula)
}
def formula(name = T.unsafe(nil), path: nil, spec: :stable, alias_path: nil, tap: nil, &block); end
sig { params(formula: ::Formula, ref: T.nilable(String), call_original: T::Boolean).void }
def stub_formula_loader(formula, ref = formula.full_name, call_original: false); end
@@ -132,25 +124,6 @@ class RSpec::Core::ExampleGroup
sig { params(name: String).returns(Pathname) }
def fixture(name); end
# `formula(...) { ... }` helper blocks can be inferred as example group
# contexts in typed specs; declare Formula DSL methods to satisfy static
# analysis.
sig { params(val: String, specs: T::Hash[Symbol, T.anything]).returns(String) }
def url(val = "", specs = {}); end
end
# `formula(...) { ... }` helper blocks can be inferred as this helper module in
# typed specs; declare Formula DSL methods to satisfy static analysis.
module Test::Helper::Formula
sig { params(val: String, specs: T::Hash[Symbol, T.anything]).returns(String) }
def url(val = "", specs = {}); end
end
# Some helper blocks are inferred as Formula instances or class contexts.
class Formula
sig { params(val: String, specs: T::Hash[Symbol, T.anything]).returns(String) }
def url(val = "", specs = {}); end
end
# The rspec-mocks RBI defines `ExpectHost#expect(target)` with a required
@@ -53,6 +53,7 @@ RSpec.describe Bottle::Filename do
let(:f) do
formula do
T.bind(self, T.class_of(Formula))
url "https://brew.sh/foo.tar.gz"
version "1.0"
end
@@ -180,7 +180,10 @@ RSpec.describe Homebrew::Bundle::MacAppStore do
describe "installing" do
before do
stub_formula_loader formula("mas") { url "mas-1.0" }
stub_formula_loader formula("mas") {
T.bind(self, T.class_of(Formula))
url "mas-1.0"
}
end
describe ".installed_app_ids" do
+4 -1
View File
@@ -34,7 +34,10 @@ RSpec.describe Homebrew::Bundle::Skipper do
it "returns true" do
allow(Hardware::CPU).to receive(:arm?).and_return(true)
allow(Homebrew).to receive(:default_prefix?).and_return(true)
stub_formula_loader formula("mysql") { url "mysql-1.0" }
stub_formula_loader formula("mysql") {
T.bind(self, T.class_of(Formula))
url "mysql-1.0"
}
expect(skipper.skip?(entry)).to be true
end
@@ -7,12 +7,14 @@ RSpec.describe Homebrew::CLI::NamedArgs do
let(:klass) { Homebrew::CLI::NamedArgs }
let(:foo) do
formula "foo" do
T.bind(self, T.class_of(Formula))
url "https://brew.sh"
version "1.0"
end
end
let(:bar) do
formula "bar" do
T.bind(self, T.class_of(Formula))
url "https://brew.sh"
version "1.0"
end
@@ -92,6 +94,7 @@ RSpec.describe Homebrew::CLI::NamedArgs do
context "when a non-core formula and a core cask are present" do
let(:non_core_formula) do
formula "foo", tap: Tap.fetch("some/tap") do
T.bind(self, T.class_of(Formula))
url "https://brew.sh"
version "1.0"
end
@@ -19,7 +19,10 @@ RSpec.describe Homebrew::Cmd::Bundle::CheckSubcommand, :no_api do
before do
Homebrew::Bundle::Checker.reset!
allow_any_instance_of(IO).to receive(:puts)
stub_formula_loader formula("mas") { url "mas-1.0" }
stub_formula_loader formula("mas") {
T.bind(self, T.class_of(Formula))
url "mas-1.0"
}
end
context "when dependencies are satisfied" do
@@ -85,7 +88,10 @@ RSpec.describe Homebrew::Cmd::Bundle::CheckSubcommand, :no_api do
before do
allow(Homebrew::Bundle::Cask).to receive(:casks).and_return([])
allow(Homebrew::Bundle::Brew).to receive_messages(upgradable_formulae: [], installed_formulae: ["abc"])
stub_formula_loader formula("abc") { url "abc-1.0" }
stub_formula_loader formula("abc") {
T.bind(self, T.class_of(Formula))
url "abc-1.0"
}
end
it "raises an error" do
@@ -101,7 +101,10 @@ RSpec.describe Homebrew::Cmd::Bundle::DumpSubcommand do
before do
ENV["HOMEBREW_BUNDLE_FILE"] = ""
stub_formula_loader formula("mas") { url "mas-1.0" }
stub_formula_loader formula("mas") {
T.bind(self, T.class_of(Formula))
url "mas-1.0"
}
allow_any_instance_of(Pathname).to receive(:exist?).and_return(true)
allow(Homebrew::Bundle).to receive(:cask_installed?).and_return(true)
allow(Cask::Caskroom).to receive(:casks).and_return([])
+1
View File
@@ -7,6 +7,7 @@ require "cmd/shared_examples/args_parse"
RSpec.describe Homebrew::Cmd::Home do
let(:testballhome) do
formula("testballhome") do
T.bind(self, T.class_of(Formula))
homepage "https://brew.sh/testballhome"
url "https://brew.sh/testballhome-1.0"
end
+13 -2
View File
@@ -13,9 +13,11 @@ RSpec.describe Homebrew::Cmd::InstallCmd do
it "prints a formula dry-run plan when asking" do
added = formula("added") do
T.bind(self, T.class_of(Formula))
url "https://brew.sh/added-1.0.tar.gz"
end
changed = formula("changed") do
T.bind(self, T.class_of(Formula))
url "https://brew.sh/changed-2.0.tar.gz"
end
added_installer = FormulaInstaller.new(added)
@@ -39,6 +41,7 @@ RSpec.describe Homebrew::Cmd::InstallCmd do
it "skips ask input when asking for only requested formulae" do
formula = formula("testball") do
T.bind(self, T.class_of(Formula))
url "https://brew.sh/testball-0.1.tar.gz"
end
formula_installer = FormulaInstaller.new(formula)
@@ -60,9 +63,11 @@ RSpec.describe Homebrew::Cmd::InstallCmd do
it "uses the requested action when asking for formulae with dependencies" do
formula = formula("changed") do
T.bind(self, T.class_of(Formula))
url "https://brew.sh/changed-2.0.tar.gz"
end
dependency = formula("dependency") do
T.bind(self, T.class_of(Formula))
url "https://brew.sh/dependency-1.0.tar.gz"
end
formula_installer = FormulaInstaller.new(formula)
@@ -261,7 +266,10 @@ RSpec.describe Homebrew::Cmd::InstallCmd do
it "prints an ask mode environment hint when installing formulae" do
cmd = klass.new(["testball"])
download_queue = instance_double(Homebrew::DownloadQueue, fetch: nil, shutdown: nil)
formula = formula("testball") { url "https://brew.sh/testball-0.1.tar.gz" }
formula = formula("testball") do
T.bind(self, T.class_of(Formula))
url "https://brew.sh/testball-0.1.tar.gz"
end
formula_installer = FormulaInstaller.new(formula)
dependants = Homebrew::Upgrade::Dependents.new(upgradeable: [], pinned: [], skipped: [])
@@ -386,7 +394,10 @@ RSpec.describe Homebrew::Cmd::InstallCmd do
it "prints a shared fetch heading and correct upgrade count", :cask do
cmd = klass.new(["codex"])
download_queue = instance_double(Homebrew::DownloadQueue, fetch: nil, shutdown: nil)
formula = formula("testball_bottle") { url "https://brew.sh/testball_bottle-0.1.tar.gz" }
formula = formula("testball_bottle") do
T.bind(self, T.class_of(Formula))
url "https://brew.sh/testball_bottle-0.1.tar.gz"
end
formula_installer = instance_double(FormulaInstaller, formula:)
cask = Cask::CaskLoader.load(cask_path("local-caffeine"))
installer = instance_double(Cask::Installer, enqueue_downloads: nil, source_download_requires_pre_fetch?: false)
+4 -1
View File
@@ -46,7 +46,10 @@ RSpec.describe Homebrew::Cmd::Reinstall do
it "asks for casks before shared prefetch when reinstalling formulae and casks" do
cmd = klass.new(["--ask", "testball", "local-caffeine"])
formula = formula("testball") { url "https://brew.sh/testball-0.1.tar.gz" }
formula = formula("testball") do
T.bind(self, T.class_of(Formula))
url "https://brew.sh/testball-0.1.tar.gz"
end
formula_installer = FormulaInstaller.new(formula)
dependants = Homebrew::Upgrade::Dependents.new(upgradeable: [], pinned: [], skipped: [])
cask = Cask::CaskLoader.load(cask_path("local-caffeine"))
@@ -40,6 +40,7 @@ RSpec.describe DescriptionCacheStore do
describe "#update_from_formula_names!" do
it "sets the formulae descriptions" do
f = formula do
T.bind(self, T.class_of(Formula))
url "url-1"
desc "desc"
end
@@ -11,6 +11,7 @@ RSpec.describe Homebrew::DevCmd::Bump do
let(:klass) { Homebrew::DevCmd::Bump }
let(:f_basic) do
formula("basic_formula") do
T.bind(self, T.class_of(Formula))
desc "Basic formula"
url "https://brew.sh/test-1.2.3.tgz"
end
@@ -8,6 +8,7 @@ RSpec.describe Language::Java do
let(:f) do
formula("openjdk") do
T.bind(self, T.class_of(Formula))
url "openjdk"
version "15.0.1"
end
@@ -15,6 +15,7 @@ RSpec.describe Language::Node do
it "calls prepend_path when node formula exists only during the first call" do
node = formula "node" do
T.bind(self, T.class_of(Formula))
url "node-test-v1.0"
end
stub_formula_loader(node)
+12 -2
View File
@@ -8,7 +8,12 @@ RSpec.describe Messages do
let(:klass) { Messages }
let(:messages) { klass.new }
let(:test_formula) { formula("foo") { url("https://brew.sh/foo-0.1.tgz") } }
let(:test_formula) do
formula("foo") do
T.bind(self, T.class_of(Formula))
url("https://brew.sh/foo-0.1.tgz")
end
end
let(:elapsed_time) { 1.1 }
describe "#record_caveats" do
@@ -56,7 +61,12 @@ RSpec.describe Messages do
end
context "when package_count is greater than one and caveats are present" do
let(:test_formula2) { formula("bar") { url("https://brew.sh/bar-0.1.tgz") } }
let(:test_formula2) do
formula("bar") do
T.bind(self, T.class_of(Formula))
url("https://brew.sh/bar-0.1.tgz")
end
end
before do
messages.record_caveats(test_formula.name, "Zsh completions were installed")
+4 -1
View File
@@ -184,7 +184,10 @@ RSpec.describe Resource do
end
it "sets its owner to be the patches' owner" do
resource.patch(:p1) { url "file:///my.patch" }
resource.patch(:p1) do
T.bind(self, Resource::Patch)
url "file:///my.patch"
end
resource.owner = owner
resource.patches.each do |p|
expect(p.resource.owner).to eq(owner)
@@ -13,7 +13,7 @@ module Test
sig {
params(
name: String, path: T.nilable(Pathname), spec: Symbol, alias_path: T.nilable(Pathname),
tap: T.nilable(Tap), block: T.nilable(T.proc.bind(Formula).void)
tap: T.nilable(Tap), block: T.nilable(T.proc.bind(::Formula).void)
).returns(::Formula)
}
def formula(name = "formula_name", path: nil, spec: :stable, alias_path: nil, tap: nil, &block)
@@ -23,6 +23,7 @@ module Test
# Use a stubbed {Formulary::FormulaLoader} to make a given formula be found
# when loading from {Formulary} with `ref`.
sig { params(formula: ::Formula, ref: T.nilable(T.any(String, Pathname)), call_original: T::Boolean).void }
def stub_formula_loader(formula, ref = formula.full_name, call_original: false)
allow(Formulary).to receive(:loader_for).and_call_original if call_original
@@ -32,20 +32,26 @@ RSpec.describe Homebrew::TestBot::Formulae do
describe "#annotate_added_dependencies" do
it "writes a warning annotation for the new recursive dependency impact" do
T.bind(self, T.untyped)
formula = formula("foo") do
T.bind(self, T.class_of(Formula))
url "foo-1.0"
depends_on "existing"
depends_on "bar"
end
existing = formula("existing") { url "existing-1.0" }
existing = formula("existing") do
T.bind(self, T.class_of(Formula))
url "existing-1.0"
end
bar = formula("bar") do
T.bind(self, T.class_of(Formula))
url "bar-1.0"
depends_on "existing"
depends_on "baz"
end
baz = formula("baz") { url "baz-1.0" }
baz = formula("baz") do
T.bind(self, T.class_of(Formula))
url "baz-1.0"
end
[existing, bar, baz].each { |f| stub_formula_loader f }
[[bar, 1_000_000], [baz, 500_000]].each do |f, size|
@@ -127,8 +133,8 @@ RSpec.describe Homebrew::TestBot::Formulae do
it "restores bottled config with InstallRenamed handling" do
Dir.mktmpdir do |tmpdir|
formula_class = Class.new(Formula)
T.unsafe(formula_class).url "foo-2.0"
T.unsafe(formula_class).version "2.0"
formula_class.url "foo-2.0"
formula_class.version "2.0"
f = formula_class.new("test-bot-config", Formulary.core_path("test-bot-config"), :stable)
config_file = HOMEBREW_PREFIX/"etc/test-bot-config.conf"
default_config_file = Pathname.new("#{config_file}.default")
+19 -4
View File
@@ -48,8 +48,13 @@ RSpec.describe Utils::Analytics do
end
describe "::report_package_event" do
let(:f) { formula { url "foo-1.0" } }
let(:package_name) { f.name }
let(:f) do
formula do
T.bind(self, T.class_of(Formula))
url "foo-1.0"
end
end
let(:package_name) { f.name }
let(:tap_name) { f.tap.name }
let(:on_request) { false }
let(:options) { "--HEAD" }
@@ -92,7 +97,12 @@ RSpec.describe Utils::Analytics do
end
describe "::report_influx" do
let(:f) { formula { url "foo-1.0" } }
let(:f) do
formula do
T.bind(self, T.class_of(Formula))
url "foo-1.0"
end
end
let(:package) { f.name }
let(:tap_name) { f.tap.name }
let(:on_request) { false }
@@ -110,7 +120,12 @@ RSpec.describe Utils::Analytics do
describe "::report_build_error" do
context "when tap is installed" do
let(:err) { BuildError.new(f, "badprg", %w[arg1 arg2], {}) }
let(:f) { formula { url "foo-1.0" } }
let(:f) do
formula do
T.bind(self, T.class_of(Formula))
url "foo-1.0"
end
end
it "reports event if BuildError raised for a formula with a public remote repository" do
allow_any_instance_of(Tap).to receive(:custom_remote?).and_return(false)