Make tests use public APIs and enforce via RuboCop

- Tests reached into private APIs with `send` and poked internal
  state via `instance_variable_get`/`instance_variable_set`, hiding
  which interfaces specs really exercise and letting visibility rot.
- Make every statically-poked method public and call it directly,
  keeping `public_send` only for dynamically-named public methods.
- Read and write state through public `attr_*` accessors instead of
  instance variable reflection.
- Add `Homebrew/NoSendInTests` and
  `Homebrew/NoInstanceVariableAccessInTests` cops scoped to
  `Library/Homebrew/test` so neither pattern creeps back in.
- Keep rare justified disables, e.g. `Module#remove_const` is
  private core Ruby and raw memoisation state has no accessor.
This commit is contained in:
Mike McQuaid
2026-07-24 19:55:53 +01:00
parent 004a640cc7
commit 56f42e0e1d
160 changed files with 3076 additions and 2763 deletions
+1 -1
View File
@@ -64,6 +64,6 @@ Do not use conventional commit prefixes such as `feat:`, `fix:`, `chore:`, `refa
9. Inline new or existing methods as methods or local variables unless they are reused 2+ times or needed for unit tests.
10. Avoid `T.must`, `T.cast`, `T.let`, `T.untyped` and `T.anything` where possible while maintaining `typed: strict`; prefer explicit nil checks, precise types and APIs that return non-nil values. If a generic top type is unavoidable, prefer `T.anything` over `T.untyped`.
11. Avoid `T.unsafe(self)` whenever possible; prefer `requires_ancestor` or similar typed module patterns.
12. Prefer `.public_send` over `.send` where possible; call methods directly when practical. Use `.send` only when a private API must be invoked.
12. Prefer `.public_send` over `.send` where possible; call methods directly when practical. Use `.send` only when a private API must be invoked. In tests, never use `.send`: make the method public and call it directly (enforced by `Homebrew/NoSendInTests`), keeping `.public_send` only for dynamically-named public methods. Likewise in tests read and write state through public `attr_*` accessors rather than `instance_variable_get`/`instance_variable_set` (enforced by `Homebrew/NoInstanceVariableAccessInTests`).
13. Keep `extend/os/*` prepends as thin as possible; put the `prepend` in the OS-specific `linux` or `macos` file rather than the shared `extend/os/*` loader with an inline `if`, and prefer putting substantive logic in shared code outside `extend/` when practical so it can be tested on all platforms instead of relying on `:needs_linux` or `:needs_macos` specs.
14. When Bash logic mirrors Ruby logic, keep both implementations in sync and add two-way comments naming the matching Ruby and Bash locations; keep matching helper filenames aligned where practical.
+14
View File
@@ -94,6 +94,20 @@ Homebrew/NegateInclude:
- "Homebrew/rubocops/**/*"
- "Homebrew/sorbet/tapioca/**/*"
Homebrew/NoInstanceVariableAccessInTests:
Description: "Use public `attr_*` accessors instead of `instance_variable_get`/`instance_variable_set` in tests."
Include:
- "Homebrew/test/**/*.rb"
Exclude:
- "Homebrew/test/support/fixtures/**/*"
Homebrew/NoSendInTests:
Description: "Make the target method public and call it directly instead of using `send` in tests."
Include:
- "Homebrew/test/**/*.rb"
Exclude:
- "Homebrew/test/support/fixtures/**/*"
Homebrew/UnreferencedLet:
Description: "Removes a lazy `let` whose name is never referenced (its block never runs)."
Include:
+1 -1
View File
@@ -147,7 +147,7 @@ module Homebrew
# Redact any token `add_auth_token_to_url!` embedded, so dry-run output doesn't leak it.
sig { params(url: T.nilable(String)).returns(String) }
private_class_method def self.redacted_url(url)
def self.redacted_url(url)
Formatter.redact_secrets(url.to_s, [GitHub::API.credentials].compact)
end
+6
View File
@@ -43,6 +43,12 @@ module Homebrew
@formula_oldnames = T.let(nil, T.nilable(T::Hash[String, String]))
end
sig {
params(formulae_by_name: T.nilable(T::Hash[String, T::Hash[Symbol, T.untyped]]))
.returns(T.nilable(T::Hash[String, T::Hash[Symbol, T.untyped]]))
}
attr_writer :formulae_by_name
sig { override.params(name: String, no_upgrade: T::Boolean, verbose: T::Boolean, options: T.untyped).returns(T::Boolean) }
def preinstall!(name, no_upgrade: false, verbose: false, **options)
new(name, options).preinstall!(no_upgrade:, verbose:)
+15 -15
View File
@@ -102,8 +102,6 @@ module Homebrew
@pool.wait_for_termination
end
private
sig { params(entries: T::Array[Installer::InstallableEntry]).returns(T::Hash[String, T::Set[String]]) }
def build_dependency_map(entries)
installed_taps = Homebrew::Bundle::Tap.installed_taps
@@ -177,6 +175,21 @@ module Homebrew
end
end
sig { params(message: String, stream: IO).void }
def write_output(message, stream: $stdout)
@output_mutex.synchronize do
# Interactive installers can leave ONLCR disabled, so use CRLF to
# ensure terminal status output returns to column 0.
if stream.tty?
stream.write(message, "\r\n")
else
stream.puts(message)
end
end
end
private
sig { params(name: String).returns(String) }
def normalize_formula_name(name)
Utils.name_from_full_name(name)
@@ -286,19 +299,6 @@ module Homebrew
end
end
sig { params(message: String, stream: IO).void }
def write_output(message, stream: $stdout)
@output_mutex.synchronize do
# Interactive installers can leave ONLCR disabled, so use CRLF to
# ensure terminal status output returns to column 0.
if stream.tty?
stream.write(message, "\r\n")
else
stream.puts(message)
end
end
end
sig { void }
def clear_tty_line
File.open("/dev/tty", "w") do |f|
+9
View File
@@ -34,6 +34,15 @@ module Homebrew
@failed_taps << tap_name
end
sig { params(failed_taps: T.nilable(T::Array[String])).returns(T.nilable(T::Array[String])) }
attr_writer :failed_taps
sig {
params(skipped_entries: T.nilable(T::Hash[Symbol, T.nilable(T::Array[String])]))
.returns(T.nilable(T::Hash[Symbol, T.nilable(T::Array[String])]))
}
attr_writer :skipped_entries
private
sig { returns(T::Hash[Symbol, T.nilable(T::Array[String])]) }
+3
View File
@@ -133,6 +133,9 @@ class CacheStoreDatabase
db.each_key(&block)
end
sig { params(db: T.nilable(T::Hash[Key, Value])).void }
attr_writer :db
private
# Lazily loaded database in read/write mode. If this method is called, a
@@ -76,6 +76,100 @@ module Cask
@bundle_ids_to_reopen ||= T.let([], T.nilable(T::Array[String]))
end
# :quit/:signal must come before :kext so the kext will not be in use by a running process
sig {
params(
bundle_ids: String,
command: T.nilable(T.class_of(SystemCommand)),
upgrade: T::Boolean,
_kwargs: T.anything,
).void
}
def uninstall_quit(*bundle_ids, command: nil, upgrade: false, **_kwargs)
bundle_ids.each do |bundle_id|
next unless running?(bundle_id)
unless T.must(User.current).gui?
opoo "Not logged into a GUI; skipping quitting application ID '#{bundle_id}'."
next
end
ohai "Quitting application '#{bundle_id}'..."
quit_succeeded = T.let(false, T::Boolean)
begin
Timeout.timeout(10) do
Kernel.loop do
next unless quit(bundle_id).success?
next if running?(bundle_id)
puts "Application '#{bundle_id}' quit successfully."
quit_succeeded = true
break
end
end
rescue Timeout::Error
opoo "Application '#{bundle_id}' did not quit. #{automation_access_instructions}"
end
bundle_ids_to_reopen << bundle_id if upgrade && quit_succeeded
end
end
# This returns T::Enumerable[[Pathname, T::Array[Pathname]]] when called without a block,
# but sorbet doesn't support overloads.
sig {
params(
action: Symbol,
paths: T::Array[T.any(Pathname, String)],
_block: T.nilable(T.proc.params(path: T.any(Pathname, String), resolved_paths: T::Array[Pathname]).void),
).returns(T.untyped)
}
def each_resolved_path(action, paths, &_block)
return enum_for(:each_resolved_path, action, paths) unless block_given?
paths.each do |path|
resolved_path = Pathname.new(path.to_s.sub(%r{^~(?=(/|$))}, Dir.home))
if resolved_path.relative?
opoo "Skipping #{Formatter.identifier(action)} for relative path '#{path}'."
next
end
if resolved_path.each_filename.any? { |part| [".", ".."].include?(part) }
opoo "Skipping #{Formatter.identifier(action)} for path with relative segments '#{path}'."
next
end
begin
resolved_paths = Pathname.glob(resolved_path).reject do |target|
next false unless undeletable?(target)
opoo "Skipping #{Formatter.identifier(action)} for undeletable path '#{target}'."
true
end
yield path, resolved_paths
rescue Errno::EPERM
raise if ::Cask::Utils.full_disk_access_enabled?
odie "Unable to remove some files. Please enable Full Disk Access for your terminal under " \
"#{::Cask::Utils.privacy_security_preference_pane("Full Disk Access")}."
end
end
end
sig { params(search: String).returns(T::Array[String]) }
def find_launchctl_with_wildcard(search)
regex = Regexp.escape(search).gsub("\\*", ".*")
system_command!("/bin/launchctl", args: ["list"])
.stdout.lines.drop(1) # skip stdout column headers
.filter_map do |line|
pid, _state, id = line.chomp.split(/\s+/)
id if pid.to_i.nonzero? && T.must(id).match?(regex)
end
end
private
sig { params(options: DirectivesType).void }
@@ -189,17 +283,6 @@ module Cask
end
end
sig { params(search: String).returns(T::Array[String]) }
def find_launchctl_with_wildcard(search)
regex = Regexp.escape(search).gsub("\\*", ".*")
system_command!("/bin/launchctl", args: ["list"])
.stdout.lines.drop(1) # skip stdout column headers
.filter_map do |line|
pid, _state, id = line.chomp.split(/\s+/)
id if pid.to_i.nonzero? && T.must(id).match?(regex)
end
end
sig { returns(String) }
def automation_access_instructions
<<~EOS
@@ -209,47 +292,6 @@ module Cask
EOS
end
# :quit/:signal must come before :kext so the kext will not be in use by a running process
sig {
params(
bundle_ids: String,
command: T.nilable(T.class_of(SystemCommand)),
upgrade: T::Boolean,
_kwargs: T.anything,
).void
}
def uninstall_quit(*bundle_ids, command: nil, upgrade: false, **_kwargs)
bundle_ids.each do |bundle_id|
next unless running?(bundle_id)
unless T.must(User.current).gui?
opoo "Not logged into a GUI; skipping quitting application ID '#{bundle_id}'."
next
end
ohai "Quitting application '#{bundle_id}'..."
quit_succeeded = T.let(false, T::Boolean)
begin
Timeout.timeout(10) do
Kernel.loop do
next unless quit(bundle_id).success?
next if running?(bundle_id)
puts "Application '#{bundle_id}' quit successfully."
quit_succeeded = true
break
end
end
rescue Timeout::Error
opoo "Application '#{bundle_id}' did not quit. #{automation_access_instructions}"
end
bundle_ids_to_reopen << bundle_id if upgrade && quit_succeeded
end
end
sig { params(bundle_id: String).returns(T::Boolean) }
def running?(bundle_id)
script = <<~JAVASCRIPT
@@ -452,48 +494,6 @@ module Cask
end
end
# This returns T::Enumerable[[Pathname, T::Array[Pathname]]] when called without a block,
# but sorbet doesn't support overloads.
sig {
params(
action: Symbol,
paths: T::Array[T.any(Pathname, String)],
_block: T.nilable(T.proc.params(path: T.any(Pathname, String), resolved_paths: T::Array[Pathname]).void),
).returns(T.untyped)
}
def each_resolved_path(action, paths, &_block)
return enum_for(:each_resolved_path, action, paths) unless block_given?
paths.each do |path|
resolved_path = Pathname.new(path.to_s.sub(%r{^~(?=(/|$))}, Dir.home))
if resolved_path.relative?
opoo "Skipping #{Formatter.identifier(action)} for relative path '#{path}'."
next
end
if resolved_path.each_filename.any? { |part| [".", ".."].include?(part) }
opoo "Skipping #{Formatter.identifier(action)} for path with relative segments '#{path}'."
next
end
begin
resolved_paths = Pathname.glob(resolved_path).reject do |target|
next false unless undeletable?(target)
opoo "Skipping #{Formatter.identifier(action)} for undeletable path '#{target}'."
true
end
yield path, resolved_paths
rescue Errno::EPERM
raise if ::Cask::Utils.full_disk_access_enabled?
odie "Unable to remove some files. Please enable Full Disk Access for your terminal under " \
"#{::Cask::Utils.privacy_security_preference_pane("Full Disk Access")}."
end
end
end
sig { params(paths: T.any(Pathname, String), command: T.class_of(SystemCommand), _kwargs: T.anything).void }
def uninstall_delete(*paths, command:, **_kwargs)
return if paths.empty?
+5 -5
View File
@@ -56,6 +56,11 @@ module Cask
end
end
sig { overridable.params(target: Pathname, source: Pathname).returns(T::Array[T.any(String, Pathname)]) }
def backup_copy_args(target, source)
["-pR", target, source]
end
private
sig {
@@ -259,11 +264,6 @@ module Cask
def undeletable?(target)
!target.parent.writable?
end
sig { overridable.params(target: Pathname, source: Pathname).returns(T::Array[T.any(String, Pathname)]) }
def backup_copy_args(target, source)
["-pR", target, source]
end
end
end
end
+5 -5
View File
@@ -80,11 +80,6 @@ module Cask
"#{@source_string}#{target_string}"
end
private
ALT_NAME_ATTRIBUTE = "com.apple.metadata:kMDItemAlternateNames"
private_constant :ALT_NAME_ATTRIBUTE
# Try to make the asset searchable under the target name. Spotlight
# respects this attribute for many filetypes, but ignores it for App
# bundles. Alfred 2.2 respects it even for App bundles.
@@ -112,6 +107,11 @@ module Cask
sudo: !file.writable?)
end
private
ALT_NAME_ATTRIBUTE = "com.apple.metadata:kMDItemAlternateNames"
private_constant :ALT_NAME_ATTRIBUTE
sig { returns(String) }
def printable_target
target.to_s.sub(/^#{Dir.home}(#{File::SEPARATOR}|$)/, "~/")
+124 -121
View File
@@ -36,6 +36,9 @@ module Cask
sig { returns(T.nilable(Download)) }
attr_reader :download
sig { params(livecheck_result: T.nilable(T.any(T::Boolean, Symbol))).void }
attr_writer :livecheck_result
sig {
params(
cask: ::Cask::Cask, download: T::Boolean, quarantine: T::Boolean,
@@ -148,6 +151,127 @@ module Cask
summary.join("\n")
end
sig {
params(
include_manual_installers: T::Boolean,
_block: T.nilable(T.proc.params(
arg0: T::Array[T.any(Artifact::Installer, Artifact::Pkg, Artifact::Relocated)],
arg1: Pathname,
).void),
).void
}
def extract_artifacts(include_manual_installers: false, &_block)
return unless online?
return if (download = self.download).nil?
artifacts = cask.artifacts.select do |artifact|
artifact.is_a?(Artifact::Pkg) ||
artifact.is_a?(Artifact::App) ||
artifact.is_a?(Artifact::Binary) ||
(include_manual_installers &&
artifact.is_a?(Artifact::Installer) &&
artifact.manual_install &&
[".app", ".pkg"].include?(artifact.path.extname.downcase))
end
if @artifacts_extracted && @tmpdir
yield artifacts, @tmpdir if block_given?
return
end
return if artifacts.empty?
@tmpdir ||= T.let(Pathname(Dir.mktmpdir("cask-audit", HOMEBREW_TEMP)), T.nilable(Pathname))
# Clean up tmp dir when @tmpdir object is destroyed
ObjectSpace.define_finalizer(
@tmpdir,
proc { FileUtils.remove_entry(@tmpdir) },
)
ohai "Downloading and extracting artifacts"
downloaded_path = download.fetch
primary_container = UnpackStrategy.detect(downloaded_path, type: @cask.container&.type, merge_xattrs: true)
return if primary_container.nil?
# If the container has any dependencies we need to install them or unpacking will fail.
if primary_container.dependencies.any?
install_options = {
show_header: true,
installed_on_request: false,
verbose: false,
}.compact
Homebrew::Install.perform_preinstall_checks_once
formula_installers = primary_container.dependencies.filter_map do |dep|
next unless dep.is_a?(Formula)
next if dep.linked?
FormulaInstaller.new(
dep,
**install_options,
)
end
valid_formula_installers = Homebrew::Install.fetch_formulae(formula_installers)
formula_installers.each do |fi|
next unless valid_formula_installers.include?(fi)
fi.install
fi.finish
end
end
# Extract the container to the temporary directory.
primary_container.extract_nestedly(to: @tmpdir, basename: downloaded_path.basename, verbose: false)
if (nested_container = @cask.container&.nested)
FileUtils.chmod_R "+rw", @tmpdir/nested_container, force: true, verbose: false
UnpackStrategy.detect(@tmpdir/nested_container, merge_xattrs: true)
.extract_nestedly(to: @tmpdir, verbose: false)
end
# Propagate quarantine attributes from the downloaded file to extracted contents.
# This is necessary because some extraction tools (like 7zr) don't preserve xattrs.
if Quarantine.available? && Quarantine.detect(downloaded_path)
Quarantine.propagate(from: downloaded_path, to: @tmpdir)
end
# Process rename operations after extraction
# Create a temporary installer to process renames in the audit directory
temp_installer = Installer.new(@cask)
temp_installer.process_rename_operations(target_dir: @tmpdir)
# Set the flag to indicate that extraction has occurred.
@artifacts_extracted = T.let(true, T.nilable(TrueClass))
# Yield the artifacts and temp directory to the block if provided.
yield artifacts, @tmpdir if block_given?
end
sig { params(min_os: T.nilable(T.any(String, MacOSVersion))).returns(T.nilable(MacOSVersion)) }
def normalize_min_os(min_os)
return if min_os.nil?
return if min_os.is_a?(String) && min_os.blank?
min_os = if min_os.is_a?(MacOSVersion)
min_os.strip_patch
else
MacOSVersion.new(min_os).strip_patch
end
# Big Sur is sometimes identified as 10.16, so we override it to the
# expected macOS version (11).
min_os = MacOSVersion.new("11") if min_os == "10.16"
min_os
rescue MacOSVersion::Error
nil
end
private
sig { void }
@@ -562,107 +686,6 @@ module Cask
end
end
sig {
params(
include_manual_installers: T::Boolean,
_block: T.nilable(T.proc.params(
arg0: T::Array[T.any(Artifact::Installer, Artifact::Pkg, Artifact::Relocated)],
arg1: Pathname,
).void),
).void
}
def extract_artifacts(include_manual_installers: false, &_block)
return unless online?
return if (download = self.download).nil?
artifacts = cask.artifacts.select do |artifact|
artifact.is_a?(Artifact::Pkg) ||
artifact.is_a?(Artifact::App) ||
artifact.is_a?(Artifact::Binary) ||
(include_manual_installers &&
artifact.is_a?(Artifact::Installer) &&
artifact.manual_install &&
[".app", ".pkg"].include?(artifact.path.extname.downcase))
end
if @artifacts_extracted && @tmpdir
yield artifacts, @tmpdir if block_given?
return
end
return if artifacts.empty?
@tmpdir ||= T.let(Pathname(Dir.mktmpdir("cask-audit", HOMEBREW_TEMP)), T.nilable(Pathname))
# Clean up tmp dir when @tmpdir object is destroyed
ObjectSpace.define_finalizer(
@tmpdir,
proc { FileUtils.remove_entry(@tmpdir) },
)
ohai "Downloading and extracting artifacts"
downloaded_path = download.fetch
primary_container = UnpackStrategy.detect(downloaded_path, type: @cask.container&.type, merge_xattrs: true)
return if primary_container.nil?
# If the container has any dependencies we need to install them or unpacking will fail.
if primary_container.dependencies.any?
install_options = {
show_header: true,
installed_on_request: false,
verbose: false,
}.compact
Homebrew::Install.perform_preinstall_checks_once
formula_installers = primary_container.dependencies.filter_map do |dep|
next unless dep.is_a?(Formula)
next if dep.linked?
FormulaInstaller.new(
dep,
**install_options,
)
end
valid_formula_installers = Homebrew::Install.fetch_formulae(formula_installers)
formula_installers.each do |fi|
next unless valid_formula_installers.include?(fi)
fi.install
fi.finish
end
end
# Extract the container to the temporary directory.
primary_container.extract_nestedly(to: @tmpdir, basename: downloaded_path.basename, verbose: false)
if (nested_container = @cask.container&.nested)
FileUtils.chmod_R "+rw", @tmpdir/nested_container, force: true, verbose: false
UnpackStrategy.detect(@tmpdir/nested_container, merge_xattrs: true)
.extract_nestedly(to: @tmpdir, verbose: false)
end
# Propagate quarantine attributes from the downloaded file to extracted contents.
# This is necessary because some extraction tools (like 7zr) don't preserve xattrs.
if Quarantine.available? && Quarantine.detect(downloaded_path)
Quarantine.propagate(from: downloaded_path, to: @tmpdir)
end
# Process rename operations after extraction
# Create a temporary installer to process renames in the audit directory
temp_installer = Installer.new(@cask)
temp_installer.process_rename_operations(target_dir: @tmpdir)
# Set the flag to indicate that extraction has occurred.
@artifacts_extracted = T.let(true, T.nilable(TrueClass))
# Yield the artifacts and temp directory to the block if provided.
yield artifacts, @tmpdir if block_given?
end
sig { void }
def audit_rosetta
return if (url = cask.url).nil?
@@ -955,26 +978,6 @@ module Cask
normalize_min_os(min_os)
end
sig { params(min_os: T.nilable(T.any(String, MacOSVersion))).returns(T.nilable(MacOSVersion)) }
def normalize_min_os(min_os)
return if min_os.nil?
return if min_os.is_a?(String) && min_os.blank?
min_os = if min_os.is_a?(MacOSVersion)
min_os.strip_patch
else
MacOSVersion.new(min_os).strip_patch
end
# Big Sur is sometimes identified as 10.16, so we override it to the
# expected macOS version (11).
min_os = MacOSVersion.new("11") if min_os == "10.16"
min_os
rescue MacOSVersion::Error
nil
end
sig { params(path: Pathname).returns(T.nilable(String)) }
def get_plist_main_binary(path)
return unless online?
+2 -2
View File
@@ -102,8 +102,6 @@ module Cask
errors
end
private
sig { params(audit: T.nilable(Audit)).returns(T::Boolean) }
def output_summary?(audit = nil)
return true if @any_named_args
@@ -113,6 +111,8 @@ module Cask
audit.errors?
end
private
sig { params(languages: T::Array[String]).returns(::Cask::Audit) }
def audit_languages(languages)
original_config = cask.config
+3
View File
@@ -147,6 +147,9 @@ module Cask
sig { returns(Pathname) }
attr_reader :path
sig { params(from_installed_caskfile: T::Boolean).void }
attr_writer :from_installed_caskfile
sig { params(path: T.any(Pathname, String), token: String).void }
def initialize(path, token: T.unsafe(nil))
super()
+7
View File
@@ -205,6 +205,13 @@ module Cask
path.stat.gid == group.gid
end
@expected_caskroom_group = T.let(nil, T.nilable(String))
class << self
sig { params(expected_caskroom_group: T.nilable(String)).void }
attr_writer :expected_caskroom_group
end
sig { returns(String) }
def self.expected_caskroom_group
"admin"
+3 -3
View File
@@ -229,6 +229,9 @@ module Cask
sig { override.returns(String) }
def download_queue_type = "Cask"
sig { override.returns(String) }
def download_name = cask.token
private
sig { void }
@@ -280,8 +283,5 @@ module Cask
def cache
Cache.path
end
sig { override.returns(String) }
def download_name = cask.token
end
end
+3
View File
@@ -56,6 +56,9 @@ module Homebrew
sig { returns(T::Array[Subcommand]) }
attr_reader :subcommands
sig { returns(T.nilable(Integer)) }
attr_reader :min_named_args
sig { params(cmd_path: Pathname).returns(T.nilable(CLI::Parser)) }
def self.from_cmd_path(cmd_path)
cmd_args_method_name = Commands.args_method_name(cmd_path)
+62 -62
View File
@@ -172,6 +172,68 @@ module Homebrew
download_queue.shutdown
end
sig { params(cask: Cask::Cask).returns(T::Array[Cask::Download]) }
def cask_downloads(cask)
ref = cask.reloadable_ref
if args.all_platforms? && cask.loaded_from_api?
opoo "Cask #{cask} was loaded from the API; cannot fetch all operating system and " \
"architecture variants. Set `HOMEBREW_NO_INSTALL_FROM_API=1` to fetch them all."
end
# With `--all-platforms`, a cask without `on_system` blocks resolves
# identically everywhere, so one combination covers the whole matrix.
cask_combinations = args.os_arch_combinations
cask_combinations = cask_combinations.first(1) if args.all_platforms? && !cask.on_system_blocks_exist?
downloads = T.let([], T::Array[Cask::Download])
enqueued_urls = Set.new
cask_combinations.each do |os, arch|
SimulateSystem.with(os:, arch:) do
loaded_cask = begin
Cask::CaskLoader.load(ref)
rescue Cask::CaskInvalidError, Cask::CaskUnreadableError
raise unless cask.on_system_blocks_exist?
end
if loaded_cask.nil? || loaded_cask.depends_on.arch&.none? { |dep_arch| dep_arch[:type] == arch }
opoo "Cask #{cask} is not supported on os #{os} and arch #{arch}"
next
end
languages = (loaded_cask.languages if args.all_platforms?)
languages = [nil] if languages.blank?
languages.each do |language|
localized_cask = loaded_cask
if language
# Reload per language: `Cask::Download` reads `sha256`/`url`
# lazily, so each download needs its own cask instance.
localized_cask = Cask::CaskLoader.load(ref)
localized_cask.config = localized_cask.config.merge(
Cask::Config.new(explicit: { languages: [language] }),
)
end
if localized_cask.url.nil? || localized_cask.sha256.nil?
opoo "Cask #{cask} is not supported on os #{os} and arch #{arch}"
next
end
next unless enqueued_urls.add?(localized_cask.url.to_s)
downloads << Cask::Download.new(
localized_cask,
quarantine: true,
require_sha: Homebrew::EnvConfig.cask_opts_require_sha?,
)
end
end
end
downloads
end
private
sig { returns(T::Boolean) }
@@ -288,68 +350,6 @@ module Homebrew
names
end
sig { params(cask: Cask::Cask).returns(T::Array[Cask::Download]) }
def cask_downloads(cask)
ref = cask.reloadable_ref
if args.all_platforms? && cask.loaded_from_api?
opoo "Cask #{cask} was loaded from the API; cannot fetch all operating system and " \
"architecture variants. Set `HOMEBREW_NO_INSTALL_FROM_API=1` to fetch them all."
end
# With `--all-platforms`, a cask without `on_system` blocks resolves
# identically everywhere, so one combination covers the whole matrix.
cask_combinations = args.os_arch_combinations
cask_combinations = cask_combinations.first(1) if args.all_platforms? && !cask.on_system_blocks_exist?
downloads = T.let([], T::Array[Cask::Download])
enqueued_urls = Set.new
cask_combinations.each do |os, arch|
SimulateSystem.with(os:, arch:) do
loaded_cask = begin
Cask::CaskLoader.load(ref)
rescue Cask::CaskInvalidError, Cask::CaskUnreadableError
raise unless cask.on_system_blocks_exist?
end
if loaded_cask.nil? || loaded_cask.depends_on.arch&.none? { |dep_arch| dep_arch[:type] == arch }
opoo "Cask #{cask} is not supported on os #{os} and arch #{arch}"
next
end
languages = (loaded_cask.languages if args.all_platforms?)
languages = [nil] if languages.blank?
languages.each do |language|
localized_cask = loaded_cask
if language
# Reload per language: `Cask::Download` reads `sha256`/`url`
# lazily, so each download needs its own cask instance.
localized_cask = Cask::CaskLoader.load(ref)
localized_cask.config = localized_cask.config.merge(
Cask::Config.new(explicit: { languages: [language] }),
)
end
if localized_cask.url.nil? || localized_cask.sha256.nil?
opoo "Cask #{cask} is not supported on os #{os} and arch #{arch}"
next
end
next unless enqueued_urls.add?(localized_cask.url.to_s)
downloads << Cask::Download.new(
localized_cask,
quarantine: true,
require_sha: Homebrew::EnvConfig.cask_opts_require_sha?,
)
end
end
end
downloads
end
sig { returns(Integer) }
def retries
@retries ||= T.let(args.retry? ? FETCH_MAX_TRIES : 1, T.nilable(Integer))
+166 -166
View File
@@ -349,39 +349,6 @@ module Homebrew
private_class_method :formula_metadata_lines, :formatted_time, :pin_path_mtime,
:formula_installs_from_source?, :cask_requirements_lines
private
sig { void }
def print_statistics
return unless HOMEBREW_CELLAR.exist?
count = Formula.racks.length
puts "#{Utils.pluralize("keg", count, include_count: true)}, #{HOMEBREW_CELLAR.dup.abv}"
end
sig { void }
def print_analytics
if args.no_named?
Utils::Analytics.output(args:)
return
end
args.named.to_formulae_and_casks_and_unavailable.each_with_index do |obj, i|
puts unless i.zero?
case obj
when Formula
Utils::Analytics.formula_output(obj, args:) if obj.core_formula?
when Cask::Cask
Utils::Analytics.cask_output(obj, args:) if obj.tap&.core_cask_tap?
when FormulaOrCaskUnavailableError
Utils::Analytics.output(filter: obj.name, args:)
else
raise
end
end
end
sig { params(quiet: T::Boolean).void }
def print_info(quiet: false)
objects = args.named.to_formulae_and_casks_and_unavailable(uniq: false)
@@ -411,25 +378,6 @@ module Homebrew
end
end
sig {
params(resolved: T::Array[[T.untyped, T.nilable(Tap)]]).returns(T::Array[[T.untyped, T.nilable(Tap)]])
}
def unique_by_display_name(resolved)
resolved.uniq do |obj, _shadowed_by|
case obj
when Formula, Cask::Cask then obj.full_name
else obj
end
end
end
sig { params(formula: Formula, user_qualified: T::Boolean).returns([Formula, T.nilable(Tap)]) }
def display_resolution(formula, user_qualified:)
return [formula, nil] if user_qualified
installed_resolution(formula)
end
sig { params(formula_or_cask: T.any(Formula, Cask::Cask), qualified_inputs: T::Set[String]).returns(T::Boolean) }
def formula_qualified_by_user?(formula_or_cask, qualified_inputs)
return false if qualified_inputs.empty?
@@ -441,24 +389,6 @@ module Homebrew
names.any? { |n| qualified_inputs.include?(n) }
end
sig { params(formula_or_cask: T.any(Formula, Cask::Cask), quiet: T::Boolean, shadowed_by: T.nilable(Tap)).void }
def info_formula_or_cask(formula_or_cask, quiet:, shadowed_by: nil)
case formula_or_cask
when Formula
if quiet
info_formula_summary(formula_or_cask)
else
info_formula(formula_or_cask, shadowed_by:)
end
when Cask::Cask
if quiet
info_cask_summary(formula_or_cask)
else
info_cask(formula_or_cask)
end
end
end
sig { params(formula: Formula).returns([Formula, T.nilable(Tap)]) }
def installed_resolution(formula)
keg = formula.installed_kegs.last
@@ -472,95 +402,6 @@ module Homebrew
[formula, nil]
end
sig { params(formula: Formula).returns(T.nilable(Formula)) }
def shadowing_installed_formula(formula)
installed_formula, shadowed_by = installed_resolution(formula)
installed_formula if shadowed_by
end
sig { params(formula: Formula, qualified_inputs: T::Set[String]).returns(Formula) }
def swap_to_installed_formula(formula, qualified_inputs)
return formula if formula_qualified_by_user?(formula, qualified_inputs)
installed_resolution(formula).first
end
sig { params(version: T.any(T::Boolean, String)).returns(Symbol) }
def json_version(version)
version_hash = {
true => :default,
"v1" => :v1,
"v2" => :v2,
}
raise UsageError, "invalid JSON version: #{version}" unless version_hash.include?(version)
version_hash[version]
end
sig { params(json: T.any(T::Boolean, String), eval_all: T::Boolean).void }
def print_json(json, eval_all)
raise FormulaOrCaskUnspecifiedError if !(eval_all || args.installed?) && args.no_named?
qualified_inputs = args.named.select { |name| name.include?("/") }.to_set
json = case json_version(json)
when :v1, :default
raise UsageError, "Cannot specify `--cask` when using `--json=v1`!" if args.cask?
formulae = if eval_all
Formula.all(eval_all:).sort
elsif args.installed?
Formula.installed.sort
else
args.named.to_formulae.map { |f| swap_to_installed_formula(f, qualified_inputs) }
end
if args.variations?
formulae.map(&:to_hash_with_variations)
else
formulae.map(&:to_hash)
end
when :v2
formulae, casks = T.let(
if eval_all
formulae = [] if args.cask?
formulae ||= Formula.all(eval_all:).sort
casks = [] if args.formula?
casks ||= Cask::Cask.all(eval_all:).sort_by(&:full_name)
[formulae, casks]
elsif args.installed?
formulae = [] if args.cask?
formulae ||= Formula.installed.sort
casks = [] if args.formula?
casks ||= Cask::Caskroom.casks.sort_by(&:full_name)
[formulae, casks]
else
named_formulae, named_casks = T.cast(
args.named.to_formulae_to_casks, [T::Array[Formula], T::Array[Cask::Cask]]
)
[named_formulae.map { |f| swap_to_installed_formula(f, qualified_inputs) }, named_casks]
end, [T::Array[Formula], T::Array[Cask::Cask]]
)
if args.variations?
{
"formulae" => formulae.map(&:to_hash_with_variations),
"casks" => casks.map(&:to_hash_with_variations),
}
else
{
"formulae" => formulae.map(&:to_hash),
"casks" => casks.map(&:to_h),
}
end
else
raise
end
puts JSON.pretty_generate(json)
end
sig { params(formula_or_cask: T.any(Formula, Cask::Cask)).returns(String) }
def github_info(formula_or_cask)
tap = T.let(nil, T.nilable(Tap))
@@ -594,13 +435,6 @@ module Homebrew
github_remote_path(remote, path.to_s)
end
sig { params(name: String, description: T.nilable(String), installed: T::Boolean).returns(String) }
def info_summary_title(name, description, installed:)
name = pretty_installed(name) if installed
"#{name}#{": #{description}" if description.present?}"
end
sig { params(formula: Formula).void }
def info_formula_summary(formula)
kegs = formula.installed_kegs
@@ -830,6 +664,172 @@ module Homebrew
Utils::Analytics.formula_output(formula, args:)
end
private
sig { void }
def print_statistics
return unless HOMEBREW_CELLAR.exist?
count = Formula.racks.length
puts "#{Utils.pluralize("keg", count, include_count: true)}, #{HOMEBREW_CELLAR.dup.abv}"
end
sig { void }
def print_analytics
if args.no_named?
Utils::Analytics.output(args:)
return
end
args.named.to_formulae_and_casks_and_unavailable.each_with_index do |obj, i|
puts unless i.zero?
case obj
when Formula
Utils::Analytics.formula_output(obj, args:) if obj.core_formula?
when Cask::Cask
Utils::Analytics.cask_output(obj, args:) if obj.tap&.core_cask_tap?
when FormulaOrCaskUnavailableError
Utils::Analytics.output(filter: obj.name, args:)
else
raise
end
end
end
sig {
params(resolved: T::Array[[T.untyped, T.nilable(Tap)]]).returns(T::Array[[T.untyped, T.nilable(Tap)]])
}
def unique_by_display_name(resolved)
resolved.uniq do |obj, _shadowed_by|
case obj
when Formula, Cask::Cask then obj.full_name
else obj
end
end
end
sig { params(formula: Formula, user_qualified: T::Boolean).returns([Formula, T.nilable(Tap)]) }
def display_resolution(formula, user_qualified:)
return [formula, nil] if user_qualified
installed_resolution(formula)
end
sig { params(formula_or_cask: T.any(Formula, Cask::Cask), quiet: T::Boolean, shadowed_by: T.nilable(Tap)).void }
def info_formula_or_cask(formula_or_cask, quiet:, shadowed_by: nil)
case formula_or_cask
when Formula
if quiet
info_formula_summary(formula_or_cask)
else
info_formula(formula_or_cask, shadowed_by:)
end
when Cask::Cask
if quiet
info_cask_summary(formula_or_cask)
else
info_cask(formula_or_cask)
end
end
end
sig { params(formula: Formula).returns(T.nilable(Formula)) }
def shadowing_installed_formula(formula)
installed_formula, shadowed_by = installed_resolution(formula)
installed_formula if shadowed_by
end
sig { params(formula: Formula, qualified_inputs: T::Set[String]).returns(Formula) }
def swap_to_installed_formula(formula, qualified_inputs)
return formula if formula_qualified_by_user?(formula, qualified_inputs)
installed_resolution(formula).first
end
sig { params(version: T.any(T::Boolean, String)).returns(Symbol) }
def json_version(version)
version_hash = {
true => :default,
"v1" => :v1,
"v2" => :v2,
}
raise UsageError, "invalid JSON version: #{version}" unless version_hash.include?(version)
version_hash[version]
end
sig { params(json: T.any(T::Boolean, String), eval_all: T::Boolean).void }
def print_json(json, eval_all)
raise FormulaOrCaskUnspecifiedError if !(eval_all || args.installed?) && args.no_named?
qualified_inputs = args.named.select { |name| name.include?("/") }.to_set
json = case json_version(json)
when :v1, :default
raise UsageError, "Cannot specify `--cask` when using `--json=v1`!" if args.cask?
formulae = if eval_all
Formula.all(eval_all:).sort
elsif args.installed?
Formula.installed.sort
else
args.named.to_formulae.map { |f| swap_to_installed_formula(f, qualified_inputs) }
end
if args.variations?
formulae.map(&:to_hash_with_variations)
else
formulae.map(&:to_hash)
end
when :v2
formulae, casks = T.let(
if eval_all
formulae = [] if args.cask?
formulae ||= Formula.all(eval_all:).sort
casks = [] if args.formula?
casks ||= Cask::Cask.all(eval_all:).sort_by(&:full_name)
[formulae, casks]
elsif args.installed?
formulae = [] if args.cask?
formulae ||= Formula.installed.sort
casks = [] if args.formula?
casks ||= Cask::Caskroom.casks.sort_by(&:full_name)
[formulae, casks]
else
named_formulae, named_casks = T.cast(
args.named.to_formulae_to_casks, [T::Array[Formula], T::Array[Cask::Cask]]
)
[named_formulae.map { |f| swap_to_installed_formula(f, qualified_inputs) }, named_casks]
end, [T::Array[Formula], T::Array[Cask::Cask]]
)
if args.variations?
{
"formulae" => formulae.map(&:to_hash_with_variations),
"casks" => casks.map(&:to_hash_with_variations),
}
else
{
"formulae" => formulae.map(&:to_hash),
"casks" => casks.map(&:to_h),
}
end
else
raise
end
puts JSON.pretty_generate(json)
end
sig { params(name: String, description: T.nilable(String), installed: T::Boolean).returns(String) }
def info_summary_title(name, description, installed:)
name = pretty_installed(name) if installed
"#{name}#{": #{description}" if description.present?}"
end
sig { params(formula: Formula, verbose: T::Boolean).returns(T::Array[String]) }
def installed_section_lines(formula, verbose: false)
siblings = formula.versioned_formulae
+25 -25
View File
@@ -90,6 +90,31 @@ module Homebrew
Homebrew.failed = args.named.present? && outdated.present?
end
sig {
params(formulae_or_casks: T::Array[T.any(Formula, Cask::Cask)]).returns(T::Array[T.any(Formula, Cask::Cask)])
}
def select_outdated(formulae_or_casks)
formulae_or_casks.select do |formula_or_cask|
if formula_or_cask.is_a?(Formula)
if minimum_version.present?
formula_outdated_kegs(formula_or_cask).present?
else
formula_or_cask.outdated?(fetch_head: args.fetch_HEAD?)
end
else
if minimum_version.present?
next MinimumVersion.cask_installed_below?(formula_or_cask, T.must(minimum_version))
end
cask_greedy = upgrade_greedy_cask?(args.greedy?, formula_or_cask)
formula_or_cask.outdated?(greedy: cask_greedy,
greedy_latest: args.greedy_latest?,
greedy_auto_updates: args.greedy_auto_updates?)
end
end
end
private
sig { params(formulae_or_casks: T::Array[T.any(Formula, Cask::Cask)]).void }
@@ -243,31 +268,6 @@ module Homebrew
[select_outdated(formulae).sort, select_outdated(casks)]
end
sig {
params(formulae_or_casks: T::Array[T.any(Formula, Cask::Cask)]).returns(T::Array[T.any(Formula, Cask::Cask)])
}
def select_outdated(formulae_or_casks)
formulae_or_casks.select do |formula_or_cask|
if formula_or_cask.is_a?(Formula)
if minimum_version.present?
formula_outdated_kegs(formula_or_cask).present?
else
formula_or_cask.outdated?(fetch_head: args.fetch_HEAD?)
end
else
if minimum_version.present?
next MinimumVersion.cask_installed_below?(formula_or_cask, T.must(minimum_version))
end
cask_greedy = upgrade_greedy_cask?(args.greedy?, formula_or_cask)
formula_or_cask.outdated?(greedy: cask_greedy,
greedy_latest: args.greedy_latest?,
greedy_auto_updates: args.greedy_auto_updates?)
end
end
end
sig { params(formula: Formula).returns(T::Array[Keg]) }
def formula_outdated_kegs(formula)
MinimumVersion.formula_outdated_kegs(formula, minimum_version, fetch_head: args.fetch_HEAD?)
+15 -15
View File
@@ -90,6 +90,21 @@ module Homebrew
print_regex_help
end
sig { params(query: String, found_matches: T::Boolean).void }
def print_missing_formula_help(query, found_matches)
return unless $stdout.tty?
return if query.match?(Search::QUERY_REGEX)
reason = MissingFormula.reason(query, silent: true)
return if reason.nil?
if found_matches
puts
puts "If you meant #{query.inspect} specifically:"
end
puts reason
end
private
sig { void }
@@ -154,21 +169,6 @@ module Homebrew
odie "No formulae or casks found for #{query.inspect}." if count.zero?
end
sig { params(query: String, found_matches: T::Boolean).void }
def print_missing_formula_help(query, found_matches)
return unless $stdout.tty?
return if query.match?(Search::QUERY_REGEX)
reason = MissingFormula.reason(query, silent: true)
return if reason.nil?
if found_matches
puts
puts "If you meant #{query.inspect} specifically:"
end
puts reason
end
end
end
end
+20 -20
View File
@@ -45,26 +45,6 @@ module Homebrew
exec_browser(*repo_urls)
end
private
sig { params(formula: Formula).returns(T.nilable(String)) }
def extract_repo_url(formula)
urls_to_check = [
formula.head&.url,
formula.stable&.url,
formula.homepage,
]
urls_to_check.each do |url|
next if url.nil?
repo_url = url_to_repo(url)
return repo_url if repo_url
end
nil
end
sig { params(url: String).returns(T.nilable(String)) }
def url_to_repo(url)
github_repo_url(url) ||
@@ -207,6 +187,26 @@ module Homebrew
rescue JSON::ParserError
nil
end
private
sig { params(formula: Formula).returns(T.nilable(String)) }
def extract_repo_url(formula)
urls_to_check = [
formula.head&.url,
formula.stable&.url,
formula.homepage,
]
urls_to_check.each do |url|
next if url.nil?
repo_url = url_to_repo(url)
return repo_url if repo_url
end
nil
end
end
end
end
+31 -31
View File
@@ -40,8 +40,6 @@ module Homebrew
end
end
private
sig { params(taps: T::Array[Tap]).void }
def print_tap_info(taps)
if taps.none?
@@ -95,9 +93,6 @@ module Homebrew
end
end
LISTING_LIMIT = 30
private_constant :LISTING_LIMIT
sig { params(tap: Tap).void }
def print_tap_listings(tap)
commands = tap.command_files
@@ -124,32 +119,6 @@ module Homebrew
end
end
sig {
params(
tap: Tap,
label: String,
all: T::Array[String],
installed: T::Array[String],
min_width: Integer,
block: T.proc.params(name: String).returns(String),
).void
}
def print_section(tap, label, all, installed, min_width:, &block)
return if all.none?
if all.size <= LISTING_LIMIT
ohai label, Formatter.columns(all.map(&block), min_width:)
elsif installed.any?
ohai label
opoo "Tap has more than #{LISTING_LIMIT} #{label.downcase}; showing only installed entries."
puts Formatter.columns(installed.map(&block), min_width:)
else
ohai label
opoo "Tap has more than #{LISTING_LIMIT} #{label.downcase} and none are installed."
puts "See: #{tap.remote}" if tap.remote.present?
end
end
sig { params(tap: Tap, name: String, installed: T::Boolean).returns(String) }
def decorate_formula(tap, name, installed:)
formula = Formulary.factory("#{tap.name}/#{name}")
@@ -187,6 +156,37 @@ module Homebrew
puts JSON.pretty_generate(hashes)
end
private
LISTING_LIMIT = 30
private_constant :LISTING_LIMIT
sig {
params(
tap: Tap,
label: String,
all: T::Array[String],
installed: T::Array[String],
min_width: Integer,
block: T.proc.params(name: String).returns(String),
).void
}
def print_section(tap, label, all, installed, min_width:, &block)
return if all.none?
if all.size <= LISTING_LIMIT
ohai label, Formatter.columns(all.map(&block), min_width:)
elsif installed.any?
ohai label
opoo "Tap has more than #{LISTING_LIMIT} #{label.downcase}; showing only installed entries."
puts Formatter.columns(installed.map(&block), min_width:)
else
ohai label
opoo "Tap has more than #{LISTING_LIMIT} #{label.downcase} and none are installed."
puts "See: #{tap.remote}" if tap.remote.present?
end
end
end
end
end
+11 -11
View File
@@ -50,6 +50,17 @@ module Homebrew
end
end
sig { void }
def donation_message
return if Settings.read("donationmessage") == "true"
ohai "Homebrew is run entirely by unpaid volunteers. Please consider donating:"
puts " #{Formatter.url("https://github.com/Homebrew/brew#-donations")}\n\n"
# Consider the message possibly missed if not a TTY.
Settings.write "donationmessage", true if $stdout.tty?
end
private
sig { void }
@@ -405,17 +416,6 @@ module Homebrew
Utils::Analytics.messages_displayed! if $stdout.tty?
end
sig { void }
def donation_message
return if Settings.read("donationmessage") == "true"
ohai "Homebrew is run entirely by unpaid volunteers. Please consider donating:"
puts " #{Formatter.url("https://github.com/Homebrew/brew#-donations")}\n\n"
# Consider the message possibly missed if not a TTY.
Settings.write "donationmessage", true if $stdout.tty?
end
sig { void }
def install_from_api_message
return if Settings.read("installfromapimessage") == "true"
+29 -29
View File
@@ -338,8 +338,6 @@ class Reporter
end
end
private
sig { params(name: String, new_name: String, new_tap: Tap).returns(T::Boolean) }
def ensure_trusted_tap_installed!(name, new_name, new_tap)
return true if new_tap.installed?
@@ -368,33 +366,6 @@ class Reporter
true
end
sig { returns(Tap) }
attr_reader :tap
sig { returns(String) }
attr_reader :initial_revision
sig { returns(String) }
attr_reader :current_revision
sig { returns(T.nilable(Pathname)) }
attr_reader :api_names_txt
sig { returns(T.nilable(Pathname)) }
attr_reader :api_names_before_txt
sig { returns(T.nilable(Pathname)) }
attr_reader :api_dir_prefix
sig {
params(api_names_txt: T.nilable(Pathname), api_names_before_txt: T.nilable(Pathname),
api_dir_prefix: T.nilable(Pathname)).returns(T::Boolean)
}
def installed_from_api?(api_names_txt = @api_names_txt, api_names_before_txt = @api_names_before_txt,
api_dir_prefix = @api_dir_prefix)
!api_names_txt.nil? && !api_names_before_txt.nil? && !api_dir_prefix.nil?
end
sig { returns(String) }
def diff
@diff ||= T.let(nil, T.nilable(String))
@@ -436,4 +407,33 @@ class Reporter
)
end
end
private
sig { returns(Tap) }
attr_reader :tap
sig { returns(String) }
attr_reader :initial_revision
sig { returns(String) }
attr_reader :current_revision
sig { returns(T.nilable(Pathname)) }
attr_reader :api_names_txt
sig { returns(T.nilable(Pathname)) }
attr_reader :api_names_before_txt
sig { returns(T.nilable(Pathname)) }
attr_reader :api_dir_prefix
sig {
params(api_names_txt: T.nilable(Pathname), api_names_before_txt: T.nilable(Pathname),
api_dir_prefix: T.nilable(Pathname)).returns(T::Boolean)
}
def installed_from_api?(api_names_txt = @api_names_txt, api_names_before_txt = @api_names_before_txt,
api_dir_prefix = @api_dir_prefix)
!api_names_txt.nil? && !api_names_before_txt.nil? && !api_dir_prefix.nil?
end
end
@@ -20,6 +20,11 @@ class ReporterHub
T.cast(@hash.fetch(key, []), T::Array[String])
end
sig { returns(T::Array[[String, String]]) }
def renamed_formulae
T.cast(@hash.fetch(:R, []), T::Array[[String, String]])
end
sig { params(reporter: Reporter, auto_update: T::Boolean).void }
def add(reporter, auto_update: false)
@reporters << reporter
+79 -79
View File
@@ -336,51 +336,6 @@ module Homebrew
show_final_upgrade_summary
end
private
sig { returns(T.nilable(String)) }
def minimum_version = args.minimum_version || args.min_version
sig { params(formula: Formula).returns(T::Boolean) }
def formula_outdated?(formula)
outdated = formula.outdated?(fetch_head: args.fetch_HEAD?)
return false if outdated && fetched_head_formula_current?(formula)
version = minimum_version
return outdated if version.blank?
outdated && MinimumVersion.formula_outdated_kegs(formula, version, fetch_head: args.fetch_HEAD?).present?
end
sig { params(formula: Formula).returns(T::Boolean) }
def fetched_head_formula_current?(formula)
return false unless args.fetch_HEAD?
return false unless formula.head?
return false unless formula.optlinked?
old_version = Keg.new(formula.opt_prefix).version
return false unless old_version.head?
formula.latest_head_pkg_version(fetch_head: true).to_s == old_version.to_s
end
sig { params(casks: T::Array[Cask::Cask], quiet: T::Boolean).returns(T::Array[Cask::Cask]) }
def minimum_version_casks(casks, quiet: args.quiet?)
version = minimum_version
return casks if version.blank?
casks.select do |cask|
if MinimumVersion.cask_installed_below?(cask, version)
true
else
unless quiet
opoo "Not upgrading #{cask.token}, the installed version is not below the minimum version #{version}"
end
false
end
end
end
sig {
params(formulae: T::Array[Formula], show_upgrade_summary: T::Boolean,
dry_run: T::Boolean).returns(T.nilable(FormulaeUpgradeContext))
@@ -624,15 +579,6 @@ module Homebrew
end
end
sig { params(title: String, items: T::Array[String]).void }
def show_final_upgrade_summary_section(title, items)
items = items.uniq
return if items.empty?
oh1 title
puts items.join("\n")
end
sig { params(formulae: T::Array[Formula], include_sizes: T::Boolean).returns(T::Array[String]) }
def formula_upgrade_descriptions(formulae, include_sizes: false)
formulae.map do |formula|
@@ -654,31 +600,6 @@ module Homebrew
end
end
sig { params(formula: Formula, old_version: PkgVersion).returns(String) }
def formula_upgrade_display_version(formula, old_version)
return formula.pkg_version.to_s if !old_version.head? || !formula.head?
return formula.pkg_version.to_s if formula.pkg_version.to_s != old_version.to_s
return "latest HEAD" unless args.fetch_HEAD?
latest_head_version = formula.latest_head_pkg_version(fetch_head: true)
return "latest HEAD" if latest_head_version.to_s == old_version.to_s
latest_head_version.to_s
end
sig { params(formula: Formula).returns(String) }
def formula_upgrade_size(formula)
return "" if args.build_from_source_formulae.include?(formula.name)
bottle = formula.bottle
return "" unless bottle
bottle.fetch_tab(quiet: !args.debug?)
return "" unless (download_size = bottle.bottle_size)
" (#{Formatter.disk_usage_readable(download_size.to_i)})"
end
sig {
params(
formulae: T::Array[Formula],
@@ -919,6 +840,85 @@ module Homebrew
ofail e
false
end
private
sig { returns(T.nilable(String)) }
def minimum_version = args.minimum_version || args.min_version
sig { params(formula: Formula).returns(T::Boolean) }
def formula_outdated?(formula)
outdated = formula.outdated?(fetch_head: args.fetch_HEAD?)
return false if outdated && fetched_head_formula_current?(formula)
version = minimum_version
return outdated if version.blank?
outdated && MinimumVersion.formula_outdated_kegs(formula, version, fetch_head: args.fetch_HEAD?).present?
end
sig { params(formula: Formula).returns(T::Boolean) }
def fetched_head_formula_current?(formula)
return false unless args.fetch_HEAD?
return false unless formula.head?
return false unless formula.optlinked?
old_version = Keg.new(formula.opt_prefix).version
return false unless old_version.head?
formula.latest_head_pkg_version(fetch_head: true).to_s == old_version.to_s
end
sig { params(casks: T::Array[Cask::Cask], quiet: T::Boolean).returns(T::Array[Cask::Cask]) }
def minimum_version_casks(casks, quiet: args.quiet?)
version = minimum_version
return casks if version.blank?
casks.select do |cask|
if MinimumVersion.cask_installed_below?(cask, version)
true
else
unless quiet
opoo "Not upgrading #{cask.token}, the installed version is not below the minimum version #{version}"
end
false
end
end
end
sig { params(title: String, items: T::Array[String]).void }
def show_final_upgrade_summary_section(title, items)
items = items.uniq
return if items.empty?
oh1 title
puts items.join("\n")
end
sig { params(formula: Formula, old_version: PkgVersion).returns(String) }
def formula_upgrade_display_version(formula, old_version)
return formula.pkg_version.to_s if !old_version.head? || !formula.head?
return formula.pkg_version.to_s if formula.pkg_version.to_s != old_version.to_s
return "latest HEAD" unless args.fetch_HEAD?
latest_head_version = formula.latest_head_pkg_version(fetch_head: true)
return "latest HEAD" if latest_head_version.to_s == old_version.to_s
latest_head_version.to_s
end
sig { params(formula: Formula).returns(String) }
def formula_upgrade_size(formula)
return "" if args.build_from_source_formulae.include?(formula.name)
bottle = formula.bottle
return "" unless bottle
bottle.fetch_tab(quiet: !args.debug?)
return "" unless (download_size = bottle.bottle_size)
" (#{Formatter.disk_usage_readable(download_size.to_i)})"
end
end
end
end
+3
View File
@@ -163,6 +163,9 @@ class Dependency
end
class << self
sig { returns(T.nilable(T::Array[T.any(String, Symbol)])) }
attr_reader :expand_stack
# Expand the dependencies of each dependent recursively, optionally yielding
# `[dependent, dep]` pairs to allow callers to apply arbitrary filters to
# the list.
+57 -57
View File
@@ -216,17 +216,6 @@ module Homebrew
end
end
private
sig { params(version: Cask::DSL::Version, cask: Cask::Cask).returns(Cask::DSL::Version) }
def shortened_version(version, cask:)
if version.before_comma == cask.version.before_comma
version
else
version.before_comma
end
end
sig { params(cask: Cask::Cask, new_version: BumpVersionParser).returns(T::Array[[Symbol, Symbol]]) }
def generate_system_options(cask, new_version)
current_os = Homebrew::SimulateSystem.current_os
@@ -375,6 +364,63 @@ module Homebrew
contents
end
sig {
params(
contents: String,
name: Symbol,
old_value: T.any(Numeric, String, Symbol),
new_value: T.any(Numeric, String, Symbol),
within: T.nilable(Symbol),
).returns(String)
}
def replace_cask_stanza_value(contents, name, old_value, new_value, within: nil)
return contents if old_value == new_value
cask_ast = Utils::AST::CaskAST.new(contents)
replacement_count = cask_ast.replace_stanza_value(name, old_value, new_value, within:)
if replacement_count.zero?
# Treat an already-applied replacement as a successful no-op so the
# per-(os, arch) loop in `replace_version_and_checksum` can yield the
# same general version more than once without raising.
return contents if cask_ast.replace_stanza_value(name, new_value, new_value, within:).positive?
raise "Could not find '#{name}' stanza with value #{old_value.inspect}!"
end
cask_ast.process
end
sig { params(cask: Cask::Cask, new_version: BumpVersionParser).void }
def check_throttle(cask, new_version:)
return unless cask.tap
throttle_rate = cask.livecheck.throttle
throttle_days = cask.livecheck.throttle_days
return if throttle_rate.nil? && throttle_days.nil?
version = new_version.arm || new_version.intel || new_version.general
return unless version.is_a?(Cask::DSL::Version)
return if Livecheck.throttle_allows_bump?(cask, version.to_s, throttle_rate:, throttle_days:)
throttle_items = []
throttle_items << "#{throttle_rate} releases on multiples of #{throttle_rate}" if throttle_rate
throttle_items << "#{throttle_days} #{Utils.pluralize("day", throttle_days)}" if throttle_days
odie "#{cask.token} should only be updated every #{throttle_items.join(" or ")}"
end
private
sig { params(version: Cask::DSL::Version, cask: Cask::Cask).returns(Cask::DSL::Version) }
def shortened_version(version, cask:)
if version.before_comma == cask.version.before_comma
version
else
version.before_comma
end
end
sig {
params(
new_version: BumpVersionParser,
@@ -434,52 +480,6 @@ module Homebrew
nil
end
sig {
params(
contents: String,
name: Symbol,
old_value: T.any(Numeric, String, Symbol),
new_value: T.any(Numeric, String, Symbol),
within: T.nilable(Symbol),
).returns(String)
}
def replace_cask_stanza_value(contents, name, old_value, new_value, within: nil)
return contents if old_value == new_value
cask_ast = Utils::AST::CaskAST.new(contents)
replacement_count = cask_ast.replace_stanza_value(name, old_value, new_value, within:)
if replacement_count.zero?
# Treat an already-applied replacement as a successful no-op so the
# per-(os, arch) loop in `replace_version_and_checksum` can yield the
# same general version more than once without raising.
return contents if cask_ast.replace_stanza_value(name, new_value, new_value, within:).positive?
raise "Could not find '#{name}' stanza with value #{old_value.inspect}!"
end
cask_ast.process
end
sig { params(cask: Cask::Cask, new_version: BumpVersionParser).void }
def check_throttle(cask, new_version:)
return unless cask.tap
throttle_rate = cask.livecheck.throttle
throttle_days = cask.livecheck.throttle_days
return if throttle_rate.nil? && throttle_days.nil?
version = new_version.arm || new_version.intel || new_version.general
return unless version.is_a?(Cask::DSL::Version)
return if Livecheck.throttle_allows_bump?(cask, version.to_s, throttle_rate:, throttle_days:)
throttle_items = []
throttle_items << "#{throttle_rate} releases on multiples of #{throttle_rate}" if throttle_rate
throttle_items << "#{throttle_days} #{Utils.pluralize("day", throttle_days)}" if throttle_days
odie "#{cask.token} should only be updated every #{throttle_items.join(" or ")}"
end
sig { params(cask: Cask::Cask, new_version: BumpVersionParser).void }
def check_pull_requests(cask, new_version:)
tap = cask.tap
+82 -82
View File
@@ -504,6 +504,88 @@ module Homebrew
end
end
sig { params(formula: Formula, new_version: String).void }
def check_throttle(formula, new_version)
tap = formula.tap
return if tap.nil?
throttle_rate = formula.livecheck.throttle
throttle_days = formula.livecheck.throttle_days
return if throttle_rate.nil? && throttle_days.nil?
return if Livecheck.throttle_allows_bump?(
formula,
new_version,
throttle_rate: throttle_rate,
throttle_days: throttle_days,
)
throttle_items = []
throttle_items << "#{throttle_rate} releases on multiples of #{throttle_rate}" if throttle_rate
throttle_items << "#{throttle_days} #{Utils.pluralize("day", throttle_days)}" if throttle_days
odie "#{formula} should only be updated every #{throttle_items.join(" or ")}"
end
sig {
params(
formula: Formula,
version: String,
resource_versions: T.nilable(T::Hash[String, T::Hash[Symbol, T.nilable(String)]]),
).returns(T::Hash[String, Symbol])
}
def update_matching_version_resources!(formula, version:, resource_versions: nil)
resource_versions ||= {}
formula.resources
.select { |r| r.livecheck.formula == :parent && resource_versions[r.name].blank? }
.to_h { |resource| [resource.name, update_resource_block!(formula, resource, version)] }
end
sig {
params(
formula: Formula,
resource_versions: T::Hash[String, T::Hash[Symbol, T.nilable(String)]],
).returns(T::Hash[String, Symbol])
}
def update_resources!(formula, resource_versions:)
results = {}
formula.resources.each do |resource|
version_data = resource_versions[resource.name]
next if version_data.blank?
current_version = version_data[:current_version]
latest_version = version_data[:latest_version]
if current_version.blank? || latest_version.blank?
opoo "Could not determine versions for resource \"#{resource.name}\""
results[resource.name] = :version_unknown
next
end
if current_version == latest_version
results[resource.name] = :up_to_date
next
end
is_downgraded = Version.new(current_version) > Version.new(latest_version)
begin
result = update_resource_block!(formula, resource, latest_version)
results[resource.name] = if result == :success && is_downgraded
:downgraded
else
result
end
rescue => e
opoo "Failed to update resource \"#{resource.name}\": #{e}"
results[resource.name] = :fetch_failed
end
end
results
end
private
sig { params(url: String).returns(T.nilable(String)) }
@@ -619,29 +701,6 @@ module Homebrew
check_pull_requests(formula, tap_remote_repo, version:) unless args.write_only?
end
sig { params(formula: Formula, new_version: String).void }
def check_throttle(formula, new_version)
tap = formula.tap
return if tap.nil?
throttle_rate = formula.livecheck.throttle
throttle_days = formula.livecheck.throttle_days
return if throttle_rate.nil? && throttle_days.nil?
return if Livecheck.throttle_allows_bump?(
formula,
new_version,
throttle_rate: throttle_rate,
throttle_days: throttle_days,
)
throttle_items = []
throttle_items << "#{throttle_rate} releases on multiples of #{throttle_rate}" if throttle_rate
throttle_items << "#{throttle_days} #{Utils.pluralize("day", throttle_days)}" if throttle_days
odie "#{formula} should only be updated every #{throttle_items.join(" or ")}"
end
sig { params(formula: Formula, new_formula_version: Version).returns(T.nilable(T::Array[String])) }
def alias_update_pair(formula, new_formula_version)
versioned_alias = formula.aliases.grep(/^.*@\d+(\.\d+)?$/).first
@@ -733,65 +792,6 @@ module Homebrew
:success
end
sig {
params(
formula: Formula,
version: String,
resource_versions: T.nilable(T::Hash[String, T::Hash[Symbol, T.nilable(String)]]),
).returns(T::Hash[String, Symbol])
}
def update_matching_version_resources!(formula, version:, resource_versions: nil)
resource_versions ||= {}
formula.resources
.select { |r| r.livecheck.formula == :parent && resource_versions[r.name].blank? }
.to_h { |resource| [resource.name, update_resource_block!(formula, resource, version)] }
end
sig {
params(
formula: Formula,
resource_versions: T::Hash[String, T::Hash[Symbol, T.nilable(String)]],
).returns(T::Hash[String, Symbol])
}
def update_resources!(formula, resource_versions:)
results = {}
formula.resources.each do |resource|
version_data = resource_versions[resource.name]
next if version_data.blank?
current_version = version_data[:current_version]
latest_version = version_data[:latest_version]
if current_version.blank? || latest_version.blank?
opoo "Could not determine versions for resource \"#{resource.name}\""
results[resource.name] = :version_unknown
next
end
if current_version == latest_version
results[resource.name] = :up_to_date
next
end
is_downgraded = Version.new(current_version) > Version.new(latest_version)
begin
result = update_resource_block!(formula, resource, latest_version)
results[resource.name] = if result == :success && is_downgraded
:downgraded
else
result
end
rescue => e
opoo "Failed to update resource \"#{resource.name}\": #{e}"
results[resource.name] = :fetch_failed
end
end
results
end
sig {
params(formula: Formula, alias_rename: T.nilable(T::Array[String]),
skip_synced_versions: T::Boolean).returns(T::Boolean)
+234 -234
View File
@@ -184,62 +184,6 @@ module Homebrew
end
end
private
sig { params(formula_or_cask: T.any(Formula, Cask::Cask)).returns(T::Boolean) }
def skip_repology?(formula_or_cask)
return true unless args.repology?
(ENV["CI"].present? && args.open_pr? && formula_or_cask.livecheck_defined?) ||
(formula_or_cask.is_a?(Formula) && formula_or_cask.versioned_formula?)
end
sig { params(formulae_and_casks: T::Array[T.any(Formula, Cask::Cask)]).void }
def handle_formulae_and_casks(formulae_and_casks)
Livecheck.load_other_tap_strategies(formulae_and_casks)
ambiguous_casks = []
if !args.formula? && !args.cask?
ambiguous_casks = formulae_and_casks
.group_by { |item| Livecheck.package_or_resource_name(item, full_name: true) }
.values
.select { |items| items.length > 1 }
.flatten
.grep(Cask::Cask)
end
ambiguous_names = []
unless args.full_name?
ambiguous_names = (formulae_and_casks - ambiguous_casks)
.group_by { |item| Livecheck.package_or_resource_name(item) }
.values
.select { |items| items.length > 1 }
.flatten
end
formulae_and_casks.each_with_index do |formula_or_cask, i|
puts if i.positive?
next if skip_ineligible_formulae!(formula_or_cask)
use_full_name = args.full_name? || ambiguous_names.include?(formula_or_cask)
name = Livecheck.package_or_resource_name(formula_or_cask, full_name: use_full_name)
repository = if formula_or_cask.is_a?(Formula)
Repology::HOMEBREW_CORE
else
Repology::HOMEBREW_CASK
end
package_data = Repology.single_package_query(name, repository:) unless skip_repology?(formula_or_cask)
retrieve_and_display_info_and_open_pr(
formula_or_cask,
name,
package_data&.values&.first || [],
ambiguous_cask: ambiguous_casks.include?(formula_or_cask),
)
end
end
sig {
params(formula_or_cask: T.any(Formula, Cask::Cask)).returns(T::Boolean)
}
@@ -268,86 +212,6 @@ module Homebrew
true
end
sig {
params(
formula_or_cask: T.any(Formula, Cask::Cask),
current: T.nilable(T.any(Version, Cask::DSL::Version)),
).returns(T.any(Version, String))
}
def livecheck_result(formula_or_cask, current)
name = Livecheck.package_or_resource_name(formula_or_cask)
referenced_formula_or_cask, = Livecheck.resolve_livecheck_reference(
formula_or_cask,
full_name: false,
debug: false,
)
# Check skip conditions for a referenced formula/cask
if referenced_formula_or_cask
skip_info = Livecheck::SkipConditions.referenced_skip_information(
referenced_formula_or_cask,
name,
full_name: false,
verbose: false,
)
end
skip_info ||= Livecheck::SkipConditions.skip_information(
formula_or_cask,
full_name: false,
verbose: false,
)
if skip_info.present?
skip_status = skip_info[:status]
skip_messages = skip_info[:messages]
skip_message = skip_messages.join("; ") if skip_messages.present?
return "error: #{skip_message}" if skip_status == "error" && skip_message
return "skipped - #{skip_message || skip_status}"
end
version_info = Livecheck.latest_version(
formula_or_cask,
referenced_formula_or_cask:,
json: true, full_name: false, verbose: true, debug: false
)
return "unable to get versions" if version_info.blank?
if !version_info.key?(:latest_throttled)
version_with_cooldown(version_info, current) || Version.new(version_info[:latest])
elsif version_info[:latest_throttled].nil?
"unable to get throttled versions"
else
Version.new(version_info[:latest_throttled])
end
rescue => e
"error: #{e}"
end
sig {
params(
formula_or_cask: T.any(Formula, Cask::Cask),
name: String,
version: T.nilable(String),
).returns T.nilable(T.any(T::Array[String], String))
}
def retrieve_pull_requests(formula_or_cask, name, version: nil)
tap_remote_repo = formula_or_cask.tap&.remote_repository || formula_or_cask.tap&.full_name
odie "unexpected nil tap remote repository" if tap_remote_repo.nil?
pull_requests = begin
GitHub.fetch_pull_requests(name, tap_remote_repo, version:)
rescue GitHub::API::ValidationFailedError => e
odebug "Error fetching pull requests for #{formula_or_cask} #{name}: #{e}"
nil
end
return if pull_requests.blank?
pull_requests.map { |pr| "#{pr["title"]} (#{Formatter.url(pr["html_url"])})" }.join(", ")
end
sig {
params(
formula_or_cask: T.any(Formula, Cask::Cask),
@@ -527,68 +391,6 @@ module Homebrew
)
end
sig {
params(
formula: Formula,
formula_latest_version: String,
).returns(T::Array[ResourceVersionInfo])
}
def collect_resource_versions(formula, formula_latest_version)
resource_versions = []
formula.resources.each do |resource|
next unless resource.livecheck_defined?
next if resource.livecheck.skip?
# Resources that reference :parent track the formula version directly
if resource.livecheck.formula == :parent
current = resource.version.to_s
resource_versions << ResourceVersionInfo.new(
name: resource.name,
current_version: current,
latest_version: formula_latest_version,
outdated: Version.new(current) < Version.new(formula_latest_version),
newer_than_upstream: Version.new(current) > Version.new(formula_latest_version),
)
next
end
resource_info = Livecheck.resource_version(
resource,
formula_latest_version,
json: true,
full_name: false,
debug: false,
quiet: true,
verbose: false,
)
if resource_info.empty? || resource_info[:status] == "error"
resource_versions << ResourceVersionInfo.new(
name: resource.name,
current_version: resource.version.to_s,
latest_version: nil,
outdated: false,
newer_than_upstream: false,
)
next
end
version_info = resource_info[:version]
next if version_info.blank?
resource_versions << ResourceVersionInfo.new(
name: resource.name,
current_version: version_info[:current],
latest_version: version_info[:latest],
outdated: version_info[:outdated] == true,
newer_than_upstream: version_info[:newer_than_upstream] == true,
)
end
resource_versions
end
sig {
params(
formula_or_cask: T.any(Formula, Cask::Cask),
@@ -876,42 +678,6 @@ module Homebrew
value.match?(LIVECHECK_MESSAGE_REGEX)
end
sig {
params(
formula: Formula,
new_version: T.nilable(T.any(Version, Cask::DSL::Version)),
).returns(T::Array[String])
}
def synced_with(formula, new_version)
synced_with = []
formula.tap&.synced_versions_formulae&.each do |synced_formulae|
next unless synced_formulae.include?(formula.name)
synced_formulae.each do |synced_formula|
synced_formula = Formulary.factory(synced_formula)
next if synced_formula == formula.name
synced_with << synced_formula.name if synced_formula.version != new_version
end
end
synced_with
end
sig { params(tap: Tap, casks: T::Boolean).returns(T::Array[T.any(Formula, Cask::Cask)]) }
def autobumped_formulae_or_casks(tap, casks: false)
autobump_list = tap.autobump
autobump_list.map do |name|
qualified_name = "#{tap.name}/#{name}"
if casks
Cask::CaskLoader.load(qualified_name)
else
Formulary.factory(qualified_name)
end
end
end
# Identifies the highest upstream version that has been released before
# the cooldown interval.
#
@@ -1028,6 +794,240 @@ module Homebrew
end
end
end
private
sig { params(formula_or_cask: T.any(Formula, Cask::Cask)).returns(T::Boolean) }
def skip_repology?(formula_or_cask)
return true unless args.repology?
(ENV["CI"].present? && args.open_pr? && formula_or_cask.livecheck_defined?) ||
(formula_or_cask.is_a?(Formula) && formula_or_cask.versioned_formula?)
end
sig { params(formulae_and_casks: T::Array[T.any(Formula, Cask::Cask)]).void }
def handle_formulae_and_casks(formulae_and_casks)
Livecheck.load_other_tap_strategies(formulae_and_casks)
ambiguous_casks = []
if !args.formula? && !args.cask?
ambiguous_casks = formulae_and_casks
.group_by { |item| Livecheck.package_or_resource_name(item, full_name: true) }
.values
.select { |items| items.length > 1 }
.flatten
.grep(Cask::Cask)
end
ambiguous_names = []
unless args.full_name?
ambiguous_names = (formulae_and_casks - ambiguous_casks)
.group_by { |item| Livecheck.package_or_resource_name(item) }
.values
.select { |items| items.length > 1 }
.flatten
end
formulae_and_casks.each_with_index do |formula_or_cask, i|
puts if i.positive?
next if skip_ineligible_formulae!(formula_or_cask)
use_full_name = args.full_name? || ambiguous_names.include?(formula_or_cask)
name = Livecheck.package_or_resource_name(formula_or_cask, full_name: use_full_name)
repository = if formula_or_cask.is_a?(Formula)
Repology::HOMEBREW_CORE
else
Repology::HOMEBREW_CASK
end
package_data = Repology.single_package_query(name, repository:) unless skip_repology?(formula_or_cask)
retrieve_and_display_info_and_open_pr(
formula_or_cask,
name,
package_data&.values&.first || [],
ambiguous_cask: ambiguous_casks.include?(formula_or_cask),
)
end
end
sig {
params(
formula_or_cask: T.any(Formula, Cask::Cask),
current: T.nilable(T.any(Version, Cask::DSL::Version)),
).returns(T.any(Version, String))
}
def livecheck_result(formula_or_cask, current)
name = Livecheck.package_or_resource_name(formula_or_cask)
referenced_formula_or_cask, = Livecheck.resolve_livecheck_reference(
formula_or_cask,
full_name: false,
debug: false,
)
# Check skip conditions for a referenced formula/cask
if referenced_formula_or_cask
skip_info = Livecheck::SkipConditions.referenced_skip_information(
referenced_formula_or_cask,
name,
full_name: false,
verbose: false,
)
end
skip_info ||= Livecheck::SkipConditions.skip_information(
formula_or_cask,
full_name: false,
verbose: false,
)
if skip_info.present?
skip_status = skip_info[:status]
skip_messages = skip_info[:messages]
skip_message = skip_messages.join("; ") if skip_messages.present?
return "error: #{skip_message}" if skip_status == "error" && skip_message
return "skipped - #{skip_message || skip_status}"
end
version_info = Livecheck.latest_version(
formula_or_cask,
referenced_formula_or_cask:,
json: true, full_name: false, verbose: true, debug: false
)
return "unable to get versions" if version_info.blank?
if !version_info.key?(:latest_throttled)
version_with_cooldown(version_info, current) || Version.new(version_info[:latest])
elsif version_info[:latest_throttled].nil?
"unable to get throttled versions"
else
Version.new(version_info[:latest_throttled])
end
rescue => e
"error: #{e}"
end
sig {
params(
formula_or_cask: T.any(Formula, Cask::Cask),
name: String,
version: T.nilable(String),
).returns T.nilable(T.any(T::Array[String], String))
}
def retrieve_pull_requests(formula_or_cask, name, version: nil)
tap_remote_repo = formula_or_cask.tap&.remote_repository || formula_or_cask.tap&.full_name
odie "unexpected nil tap remote repository" if tap_remote_repo.nil?
pull_requests = begin
GitHub.fetch_pull_requests(name, tap_remote_repo, version:)
rescue GitHub::API::ValidationFailedError => e
odebug "Error fetching pull requests for #{formula_or_cask} #{name}: #{e}"
nil
end
return if pull_requests.blank?
pull_requests.map { |pr| "#{pr["title"]} (#{Formatter.url(pr["html_url"])})" }.join(", ")
end
sig {
params(
formula: Formula,
formula_latest_version: String,
).returns(T::Array[ResourceVersionInfo])
}
def collect_resource_versions(formula, formula_latest_version)
resource_versions = []
formula.resources.each do |resource|
next unless resource.livecheck_defined?
next if resource.livecheck.skip?
# Resources that reference :parent track the formula version directly
if resource.livecheck.formula == :parent
current = resource.version.to_s
resource_versions << ResourceVersionInfo.new(
name: resource.name,
current_version: current,
latest_version: formula_latest_version,
outdated: Version.new(current) < Version.new(formula_latest_version),
newer_than_upstream: Version.new(current) > Version.new(formula_latest_version),
)
next
end
resource_info = Livecheck.resource_version(
resource,
formula_latest_version,
json: true,
full_name: false,
debug: false,
quiet: true,
verbose: false,
)
if resource_info.empty? || resource_info[:status] == "error"
resource_versions << ResourceVersionInfo.new(
name: resource.name,
current_version: resource.version.to_s,
latest_version: nil,
outdated: false,
newer_than_upstream: false,
)
next
end
version_info = resource_info[:version]
next if version_info.blank?
resource_versions << ResourceVersionInfo.new(
name: resource.name,
current_version: version_info[:current],
latest_version: version_info[:latest],
outdated: version_info[:outdated] == true,
newer_than_upstream: version_info[:newer_than_upstream] == true,
)
end
resource_versions
end
sig {
params(
formula: Formula,
new_version: T.nilable(T.any(Version, Cask::DSL::Version)),
).returns(T::Array[String])
}
def synced_with(formula, new_version)
synced_with = []
formula.tap&.synced_versions_formulae&.each do |synced_formulae|
next unless synced_formulae.include?(formula.name)
synced_formulae.each do |synced_formula|
synced_formula = Formulary.factory(synced_formula)
next if synced_formula == formula.name
synced_with << synced_formula.name if synced_formula.version != new_version
end
end
synced_with
end
sig { params(tap: Tap, casks: T::Boolean).returns(T::Array[T.any(Formula, Cask::Cask)]) }
def autobumped_formulae_or_casks(tap, casks: false)
autobump_list = tap.autobump
autobump_list.map do |name|
qualified_name = "#{tap.name}/#{name}"
if casks
Cask::CaskLoader.load(qualified_name)
else
Formulary.factory(qualified_name)
end
end
end
end
end
end
+152 -152
View File
@@ -227,8 +227,6 @@ module Homebrew
puts generate_csv(grand_totals)
end
private
sig {
params(repository_refs: T::Hash[String, [Pathname, String]], to: String)
.returns([T::Hash[String, String], T::Hash[String, T::Boolean], T::Hash[String, T.nilable(String)]])
@@ -268,118 +266,6 @@ module Homebrew
[user_names, lead_maintainers, maintainer_since_dates]
end
sig {
params(
results: T::Hash[String, T::Hash[String, T::Hash[Symbol, Integer]]],
grand_totals: T::Hash[String, T::Hash[Symbol, Integer]],
user_names: T::Hash[String, String],
lead_maintainers: T::Hash[String, T::Boolean],
maintainer_since_dates: T::Hash[String, T.nilable(String)],
to: String,
).returns(String)
}
def generate_maintainer_report_csv(results, grand_totals, user_names, lead_maintainers, maintainer_since_dates,
to)
require "csv"
rows = results.sort_by do |user, _|
qualifying_total = contribution_count(grand_totals.fetch(user).slice(*QUALIFYING_CONTRIBUTION_TYPES))
[-qualifying_total, user.downcase]
end
rows.map! do |user, user_repositories|
grand_total = grand_totals.fetch(user)
repository_qualifying_totals = user_repositories.transform_values do |counts|
contribution_count(counts.slice(*QUALIFYING_CONTRIBUTION_TYPES))
end
qualifying_total = contribution_count(grand_total.slice(*QUALIFYING_CONTRIBUTION_TYPES))
maintainer_activity_met = qualifying_total >= MAINTAINER_ACTIVITY_THRESHOLD
maintainer_since = maintainer_since_dates.fetch(user)
maintainer_since_date = Date.iso8601(maintainer_since) if maintainer_since
period_end = Date.iso8601(to)
lead_maintainer = lead_maintainers.key?(user.downcase)
lead_activity_met = lead_activity_met?(user_repositories)
new_role = if lead_activity_met &&
(lead_maintainer ||
(maintainer_since_date && maintainer_since_date <= period_end.prev_year(3)))
"Lead Maintainer"
elsif maintainer_activity_met
"Maintainer"
else
"None"
end
capped = grand_total.fetch(:merged_pr_author_hit_cap, 0).positive? ||
grand_total.fetch(:approved_pr_review_hit_cap, 0).positive?
capped ||= user_repositories.any? do |_, counts|
counts.fetch(:approved_pr_review) >= MAX_PR_SEARCH ||
counts.except(:approved_pr_review).values.any? do |count|
count >= MAX_CONTRIBUTIONS
end
end
[
user,
user_names.fetch(user),
maintainer_since,
maintainer_since_date ? [(period_end - maintainer_since_date).to_i, 0].max : nil,
*PRIMARY_REPOS.flat_map do |repository|
counts = user_repositories.fetch(repository)
[*counts.values_at(*CONTRIBUTION_TYPES.keys), repository_qualifying_totals.fetch(repository)]
end,
qualifying_total,
maintainer_activity_met,
lead_activity_met,
capped,
lead_maintainer ? "Lead Maintainer" : "Maintainer",
new_role,
]
end
CSV.generate do |csv|
csv << [
"username", "name", "since", "tenure days",
*PRIMARY_REPOS.flat_map do |repository|
repository = repository.delete_prefix("Homebrew/").delete_prefix("homebrew-")
[
"#{repository} authored", "#{repository} merged", "#{repository} PRs",
"#{repository} reviews", "#{repository} coauthored", "#{repository} total"
]
end,
"total", "maintainer met", "lead met", "capped", "role", "new role"
]
rows.each { |row| csv << row }
end
end
sig { params(repositories: T::Array[String], required: T::Boolean).returns(T::Hash[String, [Pathname, String]]) }
def prepare_contribution_repositories(repositories, required:)
require "utils/git"
repository_refs = T.let({}, T::Hash[String, [Pathname, String]])
repositories.each do |repository|
repository_path, tap = repository_path_and_tap(repository)
if repository_path && tap && !repository_path.exist?
opoo "Repository #{repository} not yet tapped! Tapping it now..."
tap.install(force: true)
end
unless repository_path&.exist?
odie "Could not find a local Git repository for #{repository}." if required
next
end
$stderr.puts "Fetching latest commits for #{repository}..."
system_command!(Utils::Git.git,
args: ["-C", repository_path, "fetch", "--quiet", "--force", "origin",
"+refs/heads/*:refs/remotes/origin/*"],
print_stderr: false)
system_command!(Utils::Git.git,
args: ["-C", repository_path, "remote", "set-head", "origin", "--auto"],
print_stderr: false)
repository_refs[repository] = [repository_path, "origin/HEAD"]
end
repository_refs
end
sig { params(repository_path: Pathname, ref: String, user: String, name: String).returns(T.nilable(String)) }
def maintainer_since(repository_path, ref, user, name)
require "utils/git"
@@ -406,12 +292,6 @@ module Homebrew
nil
end
sig { params(readme: String, user: String, name: String).returns(T::Boolean) }
def readme_mentions?(readme, user, name)
readme = readme.dup.force_encoding(Encoding::UTF_8)
readme.include?("https://github.com/#{user}") || readme.include?(name)
end
sig {
params(
organisation: String,
@@ -559,38 +439,6 @@ module Homebrew
results
end
sig {
params(
pull_request: T::Hash[String, T.untyped],
authored_pull_requests: T::Set[String],
merged_pull_requests: T::Set[String],
).void
}
def add_merged_pull_request_id(pull_request, authored_pull_requests, merged_pull_requests)
number = pull_request["number"]
return unless number.is_a?(Integer)
pull_request_id = number.to_s
authored_pull_requests << pull_request_id
merged_pull_requests << pull_request_id
end
sig {
params(
counts: T::Hash[Symbol, Integer],
authored_pull_requests: T::Set[String],
merged_pull_requests: T::Set[String],
).void
}
def update_merged_pull_request_counts(counts, authored_pull_requests, merged_pull_requests)
unless authored_pull_requests.empty?
counts[:merged_pr_author] = [authored_pull_requests.length, MAX_CONTRIBUTIONS].min
end
return if merged_pull_requests.empty?
counts[:merged_pr] = [merged_pull_requests.length, MAX_CONTRIBUTIONS].min
end
sig { params(user: String, to: String).returns(T.nilable(String)) }
def github_username_for(user, to:)
return user unless user.include?("@")
@@ -731,6 +579,158 @@ module Homebrew
counts
end
private
sig {
params(
results: T::Hash[String, T::Hash[String, T::Hash[Symbol, Integer]]],
grand_totals: T::Hash[String, T::Hash[Symbol, Integer]],
user_names: T::Hash[String, String],
lead_maintainers: T::Hash[String, T::Boolean],
maintainer_since_dates: T::Hash[String, T.nilable(String)],
to: String,
).returns(String)
}
def generate_maintainer_report_csv(results, grand_totals, user_names, lead_maintainers, maintainer_since_dates,
to)
require "csv"
rows = results.sort_by do |user, _|
qualifying_total = contribution_count(grand_totals.fetch(user).slice(*QUALIFYING_CONTRIBUTION_TYPES))
[-qualifying_total, user.downcase]
end
rows.map! do |user, user_repositories|
grand_total = grand_totals.fetch(user)
repository_qualifying_totals = user_repositories.transform_values do |counts|
contribution_count(counts.slice(*QUALIFYING_CONTRIBUTION_TYPES))
end
qualifying_total = contribution_count(grand_total.slice(*QUALIFYING_CONTRIBUTION_TYPES))
maintainer_activity_met = qualifying_total >= MAINTAINER_ACTIVITY_THRESHOLD
maintainer_since = maintainer_since_dates.fetch(user)
maintainer_since_date = Date.iso8601(maintainer_since) if maintainer_since
period_end = Date.iso8601(to)
lead_maintainer = lead_maintainers.key?(user.downcase)
lead_activity_met = lead_activity_met?(user_repositories)
new_role = if lead_activity_met &&
(lead_maintainer ||
(maintainer_since_date && maintainer_since_date <= period_end.prev_year(3)))
"Lead Maintainer"
elsif maintainer_activity_met
"Maintainer"
else
"None"
end
capped = grand_total.fetch(:merged_pr_author_hit_cap, 0).positive? ||
grand_total.fetch(:approved_pr_review_hit_cap, 0).positive?
capped ||= user_repositories.any? do |_, counts|
counts.fetch(:approved_pr_review) >= MAX_PR_SEARCH ||
counts.except(:approved_pr_review).values.any? do |count|
count >= MAX_CONTRIBUTIONS
end
end
[
user,
user_names.fetch(user),
maintainer_since,
maintainer_since_date ? [(period_end - maintainer_since_date).to_i, 0].max : nil,
*PRIMARY_REPOS.flat_map do |repository|
counts = user_repositories.fetch(repository)
[*counts.values_at(*CONTRIBUTION_TYPES.keys), repository_qualifying_totals.fetch(repository)]
end,
qualifying_total,
maintainer_activity_met,
lead_activity_met,
capped,
lead_maintainer ? "Lead Maintainer" : "Maintainer",
new_role,
]
end
CSV.generate do |csv|
csv << [
"username", "name", "since", "tenure days",
*PRIMARY_REPOS.flat_map do |repository|
repository = repository.delete_prefix("Homebrew/").delete_prefix("homebrew-")
[
"#{repository} authored", "#{repository} merged", "#{repository} PRs",
"#{repository} reviews", "#{repository} coauthored", "#{repository} total"
]
end,
"total", "maintainer met", "lead met", "capped", "role", "new role"
]
rows.each { |row| csv << row }
end
end
sig { params(repositories: T::Array[String], required: T::Boolean).returns(T::Hash[String, [Pathname, String]]) }
def prepare_contribution_repositories(repositories, required:)
require "utils/git"
repository_refs = T.let({}, T::Hash[String, [Pathname, String]])
repositories.each do |repository|
repository_path, tap = repository_path_and_tap(repository)
if repository_path && tap && !repository_path.exist?
opoo "Repository #{repository} not yet tapped! Tapping it now..."
tap.install(force: true)
end
unless repository_path&.exist?
odie "Could not find a local Git repository for #{repository}." if required
next
end
$stderr.puts "Fetching latest commits for #{repository}..."
system_command!(Utils::Git.git,
args: ["-C", repository_path, "fetch", "--quiet", "--force", "origin",
"+refs/heads/*:refs/remotes/origin/*"],
print_stderr: false)
system_command!(Utils::Git.git,
args: ["-C", repository_path, "remote", "set-head", "origin", "--auto"],
print_stderr: false)
repository_refs[repository] = [repository_path, "origin/HEAD"]
end
repository_refs
end
sig { params(readme: String, user: String, name: String).returns(T::Boolean) }
def readme_mentions?(readme, user, name)
readme = readme.dup.force_encoding(Encoding::UTF_8)
readme.include?("https://github.com/#{user}") || readme.include?(name)
end
sig {
params(
pull_request: T::Hash[String, T.untyped],
authored_pull_requests: T::Set[String],
merged_pull_requests: T::Set[String],
).void
}
def add_merged_pull_request_id(pull_request, authored_pull_requests, merged_pull_requests)
number = pull_request["number"]
return unless number.is_a?(Integer)
pull_request_id = number.to_s
authored_pull_requests << pull_request_id
merged_pull_requests << pull_request_id
end
sig {
params(
counts: T::Hash[Symbol, Integer],
authored_pull_requests: T::Set[String],
merged_pull_requests: T::Set[String],
).void
}
def update_merged_pull_request_counts(counts, authored_pull_requests, merged_pull_requests)
unless authored_pull_requests.empty?
counts[:merged_pr_author] = [authored_pull_requests.length, MAX_CONTRIBUTIONS].min
end
return if merged_pull_requests.empty?
counts[:merged_pr] = [merged_pull_requests.length, MAX_CONTRIBUTIONS].min
end
sig {
params(name: String, email: String, identity_users: T::Hash[String, String]).returns(T.nilable(String))
}
@@ -192,6 +192,28 @@ module Homebrew
end
end
sig { params(cask: Cask::Cask).returns([T::Array[T::Hash[Symbol, T.any(Symbol, String)]], T::Boolean]) }
def runners(cask:)
filtered_runners = filter_runners(cask)
filtered_macos_found = filtered_runners.keys.any? do |runner|
cask.to_hash_with_variations["variations"].key?(runner.fetch(:symbol).to_sym)
end
if filtered_macos_found
# If the cask varies on a MacOS version, test it on every possible macOS version.
[filtered_runners.keys, true]
else
macos_runners, linux_runners = filtered_runners.partition do |runner, _|
runner.fetch(:symbol) != :linux
end
selected_runners = macos_runners.group_by { |runner, _| runner.fetch(:arch) }.map do |_, runners|
random_runner(runners.to_h)
end + linux_runners.map(&:first)
[selected_runners, false]
end
end
private
sig { params(cask: Cask::Cask, os: Symbol).returns(T::Array[Symbol]) }
@@ -223,28 +245,6 @@ module Homebrew
max_runner.first
end
sig { params(cask: Cask::Cask).returns([T::Array[T::Hash[Symbol, T.any(Symbol, String)]], T::Boolean]) }
def runners(cask:)
filtered_runners = filter_runners(cask)
filtered_macos_found = filtered_runners.keys.any? do |runner|
cask.to_hash_with_variations["variations"].key?(runner.fetch(:symbol).to_sym)
end
if filtered_macos_found
# If the cask varies on a MacOS version, test it on every possible macOS version.
[filtered_runners.keys, true]
else
macos_runners, linux_runners = filtered_runners.partition do |runner, _|
runner.fetch(:symbol) != :linux
end
selected_runners = macos_runners.group_by { |runner, _| runner.fetch(:arch) }.map do |_, runners|
random_runner(runners.to_h)
end + linux_runners.map(&:first)
[selected_runners, false]
end
end
sig {
params(tap: T.nilable(Tap), labels: T::Array[String], cask_names: T::Array[String], skip_install: T::Boolean,
new_cask: T::Boolean).returns(T::Array[T::Hash[Symbol,
+36 -36
View File
@@ -129,8 +129,6 @@ module Homebrew
puts format_stanza(trash: trash_paths, delete: delete_paths, rmdir: rmdir_paths)
end
private
sig { params(cask: Cask::Cask).returns(T::Array[String]) }
def resolve_patterns_from_cask(cask)
app_artifact = cask.artifacts.find { |a| a.is_a?(Cask::Artifact::App) }
@@ -144,11 +142,6 @@ module Homebrew
end
end
sig { params(patterns: T::Array[String]).returns(String) }
def format_patterns(patterns)
patterns.map { |pattern| "\"#{pattern}\"" }.to_sentence
end
sig { params(app_artifact: Cask::Artifact::App).returns(T::Array[String]) }
def bundle_identifiers(app_artifact)
info_plist = app_artifact.target/"Contents/Info.plist"
@@ -238,35 +231,6 @@ module Homebrew
result.uniq.sort
end
sig { params(basenames: T::Array[String]).returns(T::Array[String]) }
def find_wildcard_groups(basenames)
return basenames if basenames.size <= 1
used = Array.new(basenames.size, false)
result = []
basenames.each_with_index do |name, i|
next if used[i]
group_indices = [i]
basenames.each_with_index do |other, j|
next if i == j || used[j]
next unless other.start_with?(name)
group_indices << j
end
if group_indices.size > 1
result << "#{name}*"
group_indices.each { |idx| used[idx] = true }
else
result << name
end
end
result
end
sig { params(paths: T::Array[String]).returns(T::Array[String]) }
def replace_uuids(paths)
paths.map { |p| p.gsub(UUID_PATTERN, "*") }.uniq.sort
@@ -321,6 +285,42 @@ module Homebrew
.prepend("zap ")
end
private
sig { params(patterns: T::Array[String]).returns(String) }
def format_patterns(patterns)
patterns.map { |pattern| "\"#{pattern}\"" }.to_sentence
end
sig { params(basenames: T::Array[String]).returns(T::Array[String]) }
def find_wildcard_groups(basenames)
return basenames if basenames.size <= 1
used = Array.new(basenames.size, false)
result = []
basenames.each_with_index do |name, i|
next if used[i]
group_indices = [i]
basenames.each_with_index do |other, j|
next if i == j || used[j]
next unless other.start_with?(name)
group_indices << j
end
if group_indices.size > 1
result << "#{name}*"
group_indices.each { |idx| used[idx] = true }
else
result << name
end
end
result
end
sig { params(key: String, paths: T::Array[String]).returns(String) }
def format_directive(key, paths)
if paths.size == 1
+2 -2
View File
@@ -215,8 +215,6 @@ module Homebrew
exec_browser release_url
end
private
sig { params(name: String).returns(T::Array[T::Hash[String, T.untyped]]) }
def matching_releases(name)
releases_url = "#{GitHub::API_URL}/repos/Homebrew/brew/releases?per_page=#{GitHub::MAX_PER_PAGE}"
@@ -240,6 +238,8 @@ module Homebrew
end
end
private
sig { params(releases: T::Array[T::Hash[String, T.untyped]]).returns(T::Array[String]) }
def release_urls(releases)
releases.filter_map do |release|
+38 -38
View File
@@ -264,47 +264,9 @@ module Homebrew
end
end
private
sig { params(bundle_args: T::Array[String]).returns(T::Array[String]) }
def os_bundle_args(bundle_args)
# for generic tests, remove macOS or Linux specific tests
non_linux_bundle_args(non_macos_bundle_args(bundle_args))
end
sig { params(bundle_args: T::Array[String]).returns(T::Array[String]) }
def non_macos_bundle_args(bundle_args)
bundle_args << "--tag" << "~needs_homebrew_core" if ENV["CI"]
bundle_args << "--tag" << "~needs_svnadmin" unless args.online?
bundle_args << "--tag" << "~needs_svn" unless args.online?
bundle_args << "--tag" << "~needs_macos" << "--tag" << "~cask"
end
sig { params(bundle_args: T::Array[String]).returns(T::Array[String]) }
def non_linux_bundle_args(bundle_args)
bundle_args << "--tag" << "~needs_linux" << "--tag" << "~needs_systemd"
end
sig { void }
def check_test_environment!; end
sig { params(files: T::Array[String]).returns(T::Array[String]) }
def os_files(files)
# for generic tests, remove macOS or Linux specific files
non_linux_files(non_macos_files(files))
end
sig { params(files: T::Array[String]).returns(T::Array[String]) }
def non_macos_files(files)
files.grep_v(%r{^test/(os/mac|cask)(/.*|_spec\.rb)$})
end
sig { params(files: T::Array[String]).returns(T::Array[String]) }
def non_linux_files(files)
files.grep_v(%r{^test/os/linux(/.*|_spec\.rb)$})
end
sig { returns(T::Array[String]) }
def changed_test_files
changed_files = Utils::Git.changed_files(HOMEBREW_REPOSITORY)
@@ -331,6 +293,44 @@ module Homebrew
.map(&:to_s)
end
private
sig { params(bundle_args: T::Array[String]).returns(T::Array[String]) }
def os_bundle_args(bundle_args)
# for generic tests, remove macOS or Linux specific tests
non_linux_bundle_args(non_macos_bundle_args(bundle_args))
end
sig { params(bundle_args: T::Array[String]).returns(T::Array[String]) }
def non_macos_bundle_args(bundle_args)
bundle_args << "--tag" << "~needs_homebrew_core" if ENV["CI"]
bundle_args << "--tag" << "~needs_svnadmin" unless args.online?
bundle_args << "--tag" << "~needs_svn" unless args.online?
bundle_args << "--tag" << "~needs_macos" << "--tag" << "~cask"
end
sig { params(bundle_args: T::Array[String]).returns(T::Array[String]) }
def non_linux_bundle_args(bundle_args)
bundle_args << "--tag" << "~needs_linux" << "--tag" << "~needs_systemd"
end
sig { params(files: T::Array[String]).returns(T::Array[String]) }
def os_files(files)
# for generic tests, remove macOS or Linux specific files
non_linux_files(non_macos_files(files))
end
sig { params(files: T::Array[String]).returns(T::Array[String]) }
def non_macos_files(files)
files.grep_v(%r{^test/(os/mac|cask)(/.*|_spec\.rb)$})
end
sig { params(files: T::Array[String]).returns(T::Array[String]) }
def non_linux_files(files)
files.grep_v(%r{^test/os/linux(/.*|_spec\.rb)$})
end
sig { params(filestub: String).returns(T::Array[Pathname]) }
def shared_context_test_files(filestub)
case filestub
+5 -5
View File
@@ -268,6 +268,11 @@ module Homebrew
pool.wait_for_termination
end
sig { returns(T::Hash[Downloadable, Concurrent::Promises::Future]) }
def downloads
@downloads ||= T.let({}, T.nilable(T::Hash[Downloadable, Concurrent::Promises::Future]))
end
private
sig { params(downloadable: Downloadable, check_attestation: T::Boolean).void }
@@ -344,11 +349,6 @@ module Homebrew
tty && !@dumb_tty
end
sig { returns(T::Hash[Downloadable, Concurrent::Promises::Future]) }
def downloads
@downloads ||= T.let({}, T.nilable(T::Hash[Downloadable, Concurrent::Promises::Future]))
end
sig { params(future: Concurrent::Promises::Future).returns(T.nilable(String)) }
def status_from_future(future)
case future.state
@@ -63,21 +63,6 @@ class AbstractFileDownloadStrategy < AbstractDownloadStrategy
FileUtils.ln_s target_cached_location.relative_path_from(symlink_location.dirname), symlink_location, force: true
end
private
sig { returns(String) }
def resolved_basename
_, resolved_basename = resolved_url_and_basename
resolved_basename
end
sig { returns([String, String]) }
def resolved_url_and_basename
return T.must(@resolved_url_and_basename) if defined?(@resolved_url_and_basename)
T.must(@resolved_url_and_basename = T.let([url, parse_basename(url)], T.nilable([String, String])))
end
sig { params(url: String, search_query: T::Boolean).returns(String) }
def parse_basename(url, search_query: true)
components = { path: T.let([], T::Array[String]), query: T.let([], T::Array[String]) }
@@ -125,4 +110,19 @@ class AbstractFileDownloadStrategy < AbstractDownloadStrategy
File.basename(filename)
end
private
sig { returns(String) }
def resolved_basename
_, resolved_basename = resolved_url_and_basename
resolved_basename
end
sig { returns([String, String]) }
def resolved_url_and_basename
return T.must(@resolved_url_and_basename) if defined?(@resolved_url_and_basename)
T.must(@resolved_url_and_basename = T.let([url, parse_basename(url)], T.nilable([String, String])))
end
end
@@ -146,6 +146,32 @@ class CurlDownloadStrategy < AbstractFileDownloadStrategy
[time, T.must(file_size)]
end
sig { void }
def allow_deferred_environment_expansion!
@expand_deferred_environment = true
end
# Curl options to be always passed to curl,
# with raw head calls (`curl --head`) or with actual `fetch`.
sig { returns(T::Array[String]) }
def _curl_args
args = []
args += ["-b", meta.fetch(:cookies).map { |k, v| "#{k}=#{v}" }.join(";")] if meta.key?(:cookies)
args += ["-e", meta.fetch(:referer)] if meta.key?(:referer)
args += ["--user", meta.fetch(:user)] if meta.key?(:user)
if meta.fetch(:headers, []).any? { |header| header.include?(EnvSensitive::DEFERRED_PLACEHOLDER_PREFIX) }
args += ["--max-redirs", "0"]
end
args += expand_deferred_environment_args(meta.fetch(:headers, [])).flat_map { |h| ["--header", h.strip] }
args
end
private
sig { params(timeout: T.nilable(T.any(Float, Integer))).returns([String, String]) }
@@ -262,11 +288,6 @@ class CurlDownloadStrategy < AbstractFileDownloadStrategy
curl_download resolved_url, to:, try_partial: @try_partial, timeout:
end
sig { void }
def allow_deferred_environment_expansion!
@expand_deferred_environment = true
end
sig { params(args: T::Array[String]).returns(T::Array[String]) }
def expand_deferred_environment_args(args)
return args unless @expand_deferred_environment
@@ -276,27 +297,6 @@ class CurlDownloadStrategy < AbstractFileDownloadStrategy
end
end
# Curl options to be always passed to curl,
# with raw head calls (`curl --head`) or with actual `fetch`.
sig { returns(T::Array[String]) }
def _curl_args
args = []
args += ["-b", meta.fetch(:cookies).map { |k, v| "#{k}=#{v}" }.join(";")] if meta.key?(:cookies)
args += ["-e", meta.fetch(:referer)] if meta.key?(:referer)
args += ["--user", meta.fetch(:user)] if meta.key?(:user)
if meta.fetch(:headers, []).any? { |header| header.include?(EnvSensitive::DEFERRED_PLACEHOLDER_PREFIX) }
args += ["--max-redirs", "0"]
end
args += expand_deferred_environment_args(meta.fetch(:headers, [])).flat_map { |h| ["--header", h.strip] }
args
end
sig { returns(T::Hash[Symbol, T.any(String, Symbol)]) }
def _curl_opts
meta.slice(:user_agent)
@@ -49,6 +49,30 @@ class GitDownloadStrategy < VCSDownloadStrategy
@last_commit || ""
end
sig { returns(T::Boolean) }
def ref?
silent_command("git",
args: ["--git-dir", git_dir, "rev-parse", "-q", "--verify", "--end-of-options",
"#{@ref}^{commit}"])
.success?
end
sig { returns(T::Array[String]) }
def clone_args
args = %w[clone]
case @ref_type
when :branch, :tag
args << "--branch" << @ref
end
args << "--no-checkout" << "--filter=blob:none" if partial_clone_sparse_checkout?
args << "--config" << "advice.detachedHead=false" # Silences “detached head” warning.
args << "--config" << "core.fsmonitor=false" # Prevent `fsmonitor` from watching this repository.
args << "--end-of-options" << @url << cached_location.to_s
end
private
# Read user Git config so credential helpers work for private downloads,
@@ -101,14 +125,6 @@ class GitDownloadStrategy < VCSDownloadStrategy
cached_location/".git"
end
sig { returns(T::Boolean) }
def ref?
silent_command("git",
args: ["--git-dir", git_dir, "rev-parse", "-q", "--verify", "--end-of-options",
"#{@ref}^{commit}"])
.success?
end
sig { override.returns(String) }
def current_revision
system_command("git", args: ["--git-dir", git_dir, "rev-parse", "-q", "--verify", "HEAD"],
@@ -133,22 +149,6 @@ class GitDownloadStrategy < VCSDownloadStrategy
Utils::Git.supports_partial_clone_sparse_checkout?
end
sig { returns(T::Array[String]) }
def clone_args
args = %w[clone]
case @ref_type
when :branch, :tag
args << "--branch" << @ref
end
args << "--no-checkout" << "--filter=blob:none" if partial_clone_sparse_checkout?
args << "--config" << "advice.detachedHead=false" # Silences “detached head” warning.
args << "--config" << "core.fsmonitor=false" # Prevent `fsmonitor` from watching this repository.
args << "--end-of-options" << @url << cached_location.to_s
end
sig { returns(String) }
def refspec
case @ref_type
@@ -5,6 +5,12 @@
#
# @api public
class GitHubGitDownloadStrategy < GitDownloadStrategy
sig { returns(T.nilable(String)) }
attr_reader :user
sig { returns(T.nilable(String)) }
attr_reader :repo
sig { params(url: String, name: String, version: T.nilable(Version), meta: T.untyped).void }
def initialize(url, name, version, **meta)
super
+3 -2
View File
@@ -187,8 +187,9 @@ module Downloadable
download_strategy.new(primary_url, download_name, version,
mirrors:, cache:, **T.must(@url).specs).tap do |downloader|
if AbstractDownloadStrategy.expand_deferred_environment_for?(downloader)
downloader.send(:allow_deferred_environment_expansion!)
if downloader.is_a?(CurlDownloadStrategy) &&
AbstractDownloadStrategy.expand_deferred_environment_for?(downloader)
downloader.allow_deferred_environment_expansion!
end
end
end
@@ -41,6 +41,11 @@ module OS
Dependency.new(GLIBC, [:implicit])
end
sig { returns(T::Hash[String, T::Set[String]]) }
def global_dep_tree
@@global_dep_tree
end
private
GLIBC = "glibc"
@@ -114,11 +119,6 @@ module OS
@@global_dep_tree = T.let({}, T::Hash[String, T::Set[String]])
@@building_global_dep_tree = T.let(false, T::Boolean)
sig { returns(T::Hash[String, T::Set[String]]) }
def global_dep_tree
@@global_dep_tree
end
sig { void }
def building_global_dep_tree!
@@building_global_dep_tree = true
@@ -11,18 +11,6 @@ module OS
requires_ancestor { Homebrew::DevCmd::Tests }
private
sig { params(bundle_args: T::Array[String]).returns(T::Array[String]) }
def os_bundle_args(bundle_args)
non_macos_bundle_args(bundle_args)
end
sig { params(files: T::Array[String]).returns(T::Array[String]) }
def os_files(files)
non_macos_files(files)
end
sig { void }
def check_test_environment!
super
@@ -37,6 +25,18 @@ module OS
end
::Sandbox.ensure_sandbox_available!
end
private
sig { params(bundle_args: T::Array[String]).returns(T::Array[String]) }
def os_bundle_args(bundle_args)
non_macos_bundle_args(bundle_args)
end
sig { params(files: T::Array[String]).returns(T::Array[String]) }
def os_files(files)
non_macos_files(files)
end
end
end
end
+7 -7
View File
@@ -176,13 +176,6 @@ module OS
implementation.run { super }
end
private
sig { params(args: T::Array[T.any(String, ::Pathname)], tmpdir: String).returns(T::Array[T.any(String, ::Pathname)]) }
def sandbox_command(args, tmpdir)
implementation.command(args, tmpdir)
end
sig { params(tmpdir: String).returns(T::Array[String]) }
def bubblewrap_args(tmpdir)
bubblewrap.arguments(tmpdir)
@@ -193,6 +186,13 @@ module OS
bubblewrap.writable_paths
end
private
sig { params(args: T::Array[T.any(String, ::Pathname)], tmpdir: String).returns(T::Array[T.any(String, ::Pathname)]) }
def sandbox_command(args, tmpdir)
implementation.command(args, tmpdir)
end
sig { void }
def apply_sandbox
sandbox = implementation
@@ -369,8 +369,6 @@ class Sandbox
end
end
private
sig { params(abi: Integer).returns([String, Integer, Integer]) }
def ruleset_attributes(abi)
allowed_access_fs = WRITE_ACCESS_FS
@@ -397,6 +395,17 @@ class Sandbox
[attributes.pack("Q*"), handled_access_fs, allowed_access_fs]
end
sig { params(denied_paths: T::Array[::Pathname]).returns(T::Array[String]) }
def readable_paths(denied_paths)
return [] if denied_paths.empty? || denied_paths.include?(root_path)
root_path.children.sort.each_with_object([]) do |path, paths|
add_readable_path(path, denied_paths, paths)
end
end
private
sig { params(ruleset_fd: Integer, path: String, allowed_access: Integer).void }
def add_path_rule(ruleset_fd, path, allowed_access)
path_fd = open_path(path)
@@ -424,15 +433,6 @@ class Sandbox
end
end
sig { params(denied_paths: T::Array[::Pathname]).returns(T::Array[String]) }
def readable_paths(denied_paths)
return [] if denied_paths.empty? || denied_paths.include?(root_path)
root_path.children.sort.each_with_object([]) do |path, paths|
add_readable_path(path, denied_paths, paths)
end
end
sig { params(path: ::Pathname, denied_paths: T::Array[::Pathname], paths: T::Array[String]).void }
def add_readable_path(path, denied_paths, paths)
return if denied_paths.include?(path)
@@ -20,8 +20,6 @@ module OS
requires_ancestor { Utils::Bottles::Collector }
private
sig {
params(tag: Utils::Bottles::Tag,
no_older_versions: T::Boolean).returns(T.nilable(Utils::Bottles::Tag))
@@ -38,6 +36,8 @@ module OS
end
end
private
# Find a bottle built for a previous version of macOS.
sig { params(tag: Utils::Bottles::Tag).returns(T.nilable(Utils::Bottles::Tag)) }
def find_older_compatible_tag(tag)
+26 -25
View File
@@ -150,7 +150,7 @@ class Formula
#
# @api public
sig { returns(T.nilable(Tap)) }
attr_reader :tap
attr_accessor :tap
# The stable (and default) {SoftwareSpec} for this {Formula}.
# This contains all the attributes (e.g. URL, checksum) that apply to the
@@ -176,8 +176,6 @@ class Formula
sig { returns(SoftwareSpec) }
attr_reader :active_spec
protected :active_spec
# A symbol to indicate currently active {SoftwareSpec}.
# It's either `:stable` or `:head`.
# @see #active_spec
@@ -215,7 +213,7 @@ class Formula
#
# @api public
sig { returns(T.nilable(Pathname)) }
attr_reader :buildpath
attr_accessor :buildpath
# The current working directory during tests.
# Will only be non-`nil` inside {.test}.
@@ -882,6 +880,9 @@ class Formula
end
end
sig { params(oldnames: T.nilable(T::Array[String])).void }
attr_writer :oldnames
# All aliases for the formula.
#
# @api internal
@@ -3669,6 +3670,27 @@ class Formula
T.must(bottle).tab_attributes
end
# Common environment variables used by sandboxed build, test and postinstall phases.
sig { params(home: Pathname).returns(T::Hash[Symbol, String]) }
def common_sandbox_env(home)
{
_JAVA_OPTIONS: "-Duser.home=#{HOMEBREW_CACHE}/java_cache",
GOCACHE: "#{HOMEBREW_CACHE}/go_cache",
GIT_CONFIG_GLOBAL: Utils::Git.no_global_config_file,
GIT_TERMINAL_PROMPT: "0",
GOENV: "off",
GOPATH: "#{HOMEBREW_CACHE}/go_mod_cache",
CARGO_HOME: "#{HOMEBREW_CACHE}/cargo_cache",
BUNDLE_COOLDOWN: Homebrew::RELEASE_COOLDOWN_DAYS.to_s,
PIP_CACHE_DIR: "#{HOMEBREW_CACHE}/pip_cache",
PIP_CONFIG_FILE: File::NULL,
NPM_CONFIG_USERCONFIG: File::NULL,
CURL_HOME: ENV.fetch("CURL_HOME") { home.to_s },
PYTHONDONTWRITEBYTECODE: "1",
XDG_CONFIG_HOME: "#{home}/.config",
}
end
private
sig { void }
@@ -3715,27 +3737,6 @@ class Formula
exit! 1 # never gets here unless exec threw or failed
end
# Common environment variables used by sandboxed build, test and postinstall phases.
sig { params(home: Pathname).returns(T::Hash[Symbol, String]) }
def common_sandbox_env(home)
{
_JAVA_OPTIONS: "-Duser.home=#{HOMEBREW_CACHE}/java_cache",
GOCACHE: "#{HOMEBREW_CACHE}/go_cache",
GIT_CONFIG_GLOBAL: Utils::Git.no_global_config_file,
GIT_TERMINAL_PROMPT: "0",
GOENV: "off",
GOPATH: "#{HOMEBREW_CACHE}/go_mod_cache",
CARGO_HOME: "#{HOMEBREW_CACHE}/cargo_cache",
BUNDLE_COOLDOWN: Homebrew::RELEASE_COOLDOWN_DAYS.to_s,
PIP_CACHE_DIR: "#{HOMEBREW_CACHE}/pip_cache",
PIP_CONFIG_FILE: File::NULL,
NPM_CONFIG_USERCONFIG: File::NULL,
CURL_HOME: ENV.fetch("CURL_HOME") { home.to_s },
PYTHONDONTWRITEBYTECODE: "1",
XDG_CONFIG_HOME: "#{home}/.config",
}
end
sig { params(interactive: T::Boolean, debug_symbols: T::Boolean, _block: T.proc.params(arg0: Mktemp).void).void }
def stage(interactive: false, debug_symbols: false, &_block)
active_spec.stage(debug_symbols:) do |staging|
+51 -51
View File
@@ -1236,57 +1236,6 @@ module Homebrew
end
end
private
sig { params(message: String, location: T.nilable(Homebrew::SourceLocation), corrected: T::Boolean).void }
def problem(message, location: nil, corrected: false)
@problems << ({ message:, location:, corrected: })
end
sig { params(message: String, location: T.nilable(Homebrew::SourceLocation), corrected: T::Boolean).void }
def new_formula_problem(message, location: nil, corrected: false)
@new_formula_problems << ({ message:, location:, corrected: })
end
sig { params(repo_owner: String).returns(T::Boolean) }
def self_submission?(repo_owner)
return false if repo_owner.blank?
SharedAudits.self_submission_for_repo_owner?(repo_owner)
end
sig { params(formula: Formula).returns(T::Boolean) }
def head_only?(formula)
!!formula.head && formula.stable.nil?
end
sig { params(formula: Formula).returns(T::Boolean) }
def linux_only_gcc_dep?(formula)
odie "`#linux_only_gcc_dep?` works only on Linux!" if Homebrew::SimulateSystem.simulating_or_running_on_macos?
return false if formula.deps.none? { |dep| dep.name == "gcc" && !dep.implicit? }
variations = formula.to_hash_with_variations["variations"]
# The formula has no variations, so all OS-version-arch triples depend on GCC.
return false if variations.blank?
MacOSVersion::SYMBOLS.keys.product(OnSystem::ARCH_OPTIONS).each do |os, arch|
bottle_tag = Utils::Bottles::Tag.new(system: os, arch:)
next unless bottle_tag.valid_combination?
variation_dependencies = variations.dig(bottle_tag.to_sym, "dependencies")
# This variation either:
# 1. does not exist
# 2. has no variation-specific dependencies
# In either case, it matches Linux. We must check for `nil` because an empty
# array indicates that this variation does not depend on GCC.
return false if variation_dependencies.nil?
# We found a non-Linux variation that depends on GCC.
return false if variation_dependencies.include?("gcc")
end
true
end
sig { params(tap: Tap, only_names: T::Array[String]).returns(T::Array[Pathname]) }
def changed_formulae_paths(tap, only_names: [].freeze)
return [] unless tap.git?
@@ -1369,6 +1318,57 @@ module Homebrew
@committed_version_info_cache[formula.full_name] = [previous_version_info, base_ref_version_info]
end
private
sig { params(message: String, location: T.nilable(Homebrew::SourceLocation), corrected: T::Boolean).void }
def problem(message, location: nil, corrected: false)
@problems << ({ message:, location:, corrected: })
end
sig { params(message: String, location: T.nilable(Homebrew::SourceLocation), corrected: T::Boolean).void }
def new_formula_problem(message, location: nil, corrected: false)
@new_formula_problems << ({ message:, location:, corrected: })
end
sig { params(repo_owner: String).returns(T::Boolean) }
def self_submission?(repo_owner)
return false if repo_owner.blank?
SharedAudits.self_submission_for_repo_owner?(repo_owner)
end
sig { params(formula: Formula).returns(T::Boolean) }
def head_only?(formula)
!!formula.head && formula.stable.nil?
end
sig { params(formula: Formula).returns(T::Boolean) }
def linux_only_gcc_dep?(formula)
odie "`#linux_only_gcc_dep?` works only on Linux!" if Homebrew::SimulateSystem.simulating_or_running_on_macos?
return false if formula.deps.none? { |dep| dep.name == "gcc" && !dep.implicit? }
variations = formula.to_hash_with_variations["variations"]
# The formula has no variations, so all OS-version-arch triples depend on GCC.
return false if variations.blank?
MacOSVersion::SYMBOLS.keys.product(OnSystem::ARCH_OPTIONS).each do |os, arch|
bottle_tag = Utils::Bottles::Tag.new(system: os, arch:)
next unless bottle_tag.valid_combination?
variation_dependencies = variations.dig(bottle_tag.to_sym, "dependencies")
# This variation either:
# 1. does not exist
# 2. has no variation-specific dependencies
# In either case, it matches Linux. We must check for `nil` because an empty
# array indicates that this variation does not depend on GCC.
return false if variation_dependencies.nil?
# We found a non-Linux variation that depends on GCC.
return false if variation_dependencies.include?("gcc")
end
true
end
sig { params(tap: Tap).returns(String) }
def git_audit_base_ref(tap)
@git_audit_base_ref_cache ||= T.let({}, T.nilable(T::Hash[Pathname, T.nilable(String)]))
+3
View File
@@ -49,6 +49,9 @@ class FormulaInstaller
sig { returns(Homebrew::DownloadQueue) }
attr_accessor :download_queue
sig { params(ran_prelude: T::Boolean).void }
attr_writer :ran_prelude
sig {
params(
formula: Formula,
+143 -143
View File
@@ -141,149 +141,6 @@ class GitHubPackages
version_rebuild
end
private
IMAGE_CONFIG_SCHEMA_URI = "https://opencontainers.org/schema/image/config"
IMAGE_INDEX_SCHEMA_URI = "https://opencontainers.org/schema/image/index"
IMAGE_LAYOUT_SCHEMA_URI = "https://opencontainers.org/schema/image/layout"
IMAGE_MANIFEST_SCHEMA_URI = "https://opencontainers.org/schema/image/manifest"
GITHUB_PACKAGE_TYPE = "homebrew_bottle"
private_constant :IMAGE_CONFIG_SCHEMA_URI, :IMAGE_INDEX_SCHEMA_URI, :IMAGE_LAYOUT_SCHEMA_URI,
:IMAGE_MANIFEST_SCHEMA_URI, :GITHUB_PACKAGE_TYPE
sig { void }
def load_schemas!
schema_uri("content-descriptor",
"https://opencontainers.org/schema/image/content-descriptor.json")
schema_uri("defs", %w[
https://opencontainers.org/schema/defs.json
https://opencontainers.org/schema/descriptor/defs.json
https://opencontainers.org/schema/image/defs.json
https://opencontainers.org/schema/image/descriptor/defs.json
https://opencontainers.org/schema/image/index/defs.json
https://opencontainers.org/schema/image/manifest/defs.json
])
schema_uri("defs-descriptor", %w[
https://opencontainers.org/schema/descriptor.json
https://opencontainers.org/schema/defs-descriptor.json
https://opencontainers.org/schema/descriptor/defs-descriptor.json
https://opencontainers.org/schema/image/defs-descriptor.json
https://opencontainers.org/schema/image/descriptor/defs-descriptor.json
https://opencontainers.org/schema/image/index/defs-descriptor.json
https://opencontainers.org/schema/image/manifest/defs-descriptor.json
https://opencontainers.org/schema/index/defs-descriptor.json
])
schema_uri("config-schema", IMAGE_CONFIG_SCHEMA_URI)
schema_uri("image-index-schema", IMAGE_INDEX_SCHEMA_URI)
schema_uri("image-layout-schema", IMAGE_LAYOUT_SCHEMA_URI)
schema_uri("image-manifest-schema", IMAGE_MANIFEST_SCHEMA_URI)
end
sig { params(basename: String, uris: T.any(String, T::Array[String])).void }
def schema_uri(basename, uris)
# The current `main` version has an invalid JSON schema.
# Going forward, this should probably be pinned to tags.
# We currently use features newer than the last one (v1.0.2).
url = "https://raw.githubusercontent.com/opencontainers/image-spec/170393e57ed656f7f81c3070bfa8c3346eaa0a5a/schema/#{basename}.json"
out = Utils::Curl.curl_output(url).stdout
json = JSON.parse(out)
@schema_json ||= T.let({}, T.nilable(T::Hash[String, T::Hash[String, T.untyped]]))
Array(uris).each do |uri|
@schema_json[uri] = json
end
end
T::Sig::WithoutRuntime.sig { params(uri: URI::Generic).returns(T.nilable(T::Hash[String, T.untyped])) }
def schema_resolver(uri)
@schema_json&.fetch(uri.to_s.gsub(/#.*/, ""))
end
sig { params(schema_uri: String, json: T::Hash[T.any(String, Symbol), T.untyped]).void }
def validate_schema!(schema_uri, json)
schema = JSONSchemer.schema(@schema_json&.fetch(schema_uri), ref_resolver: method(:schema_resolver))
json = json.deep_stringify_keys
return if schema.valid?(json)
puts
ofail "#{Formatter.url(schema_uri)} JSON schema validation failed!"
oh1 "Errors"
puts schema.validate(json).to_a.inspect
oh1 "JSON"
puts json.inspect
exit 1
end
sig { params(user: String, token: String, skopeo: Pathname, image_uri: String, root: Pathname, dry_run: T::Boolean).void }
def download(user, token, skopeo, image_uri, root, dry_run:)
puts
args = ["copy", "--all", image_uri.to_s, "oci:#{root}"]
if dry_run
puts "#{skopeo} #{args.join(" ")} --src-creds=#{user}:$HOMEBREW_GITHUB_PACKAGES_TOKEN"
else
args << "--src-creds=#{user}:#{token}"
system_command!(skopeo, verbose: true, print_stdout: true, args:)
end
end
sig {
params(
user: String, token: String, skopeo: Pathname, _formula_full_name: String,
bottle_hash: T::Hash[String, T.untyped], keep_old: T::Boolean, dry_run: T::Boolean, warn_on_error: T::Boolean
).returns(
T.nilable([String, String, String, Version, Integer, String, String, String, T::Boolean]),
)
}
def preupload_check(user, token, skopeo, _formula_full_name, bottle_hash, keep_old:, dry_run:, warn_on_error:)
formula_name = bottle_hash["formula"]["name"]
_, org, repo, = *bottle_hash["bottle"]["root_url"].match(URL_REGEX)
repo = "homebrew-#{repo}" unless repo.start_with?("homebrew-")
version = Version.new(bottle_hash["formula"]["pkg_version"])
rebuild = bottle_hash["bottle"]["rebuild"].to_i
version_rebuild = GitHubPackages.version_rebuild(version, rebuild)
image_name = GitHubPackages.image_formula_name(formula_name)
image_tag = GitHubPackages.image_version_rebuild(version_rebuild)
image_uri = "#{GitHubPackages.root_url(org, repo, DOCKER_PREFIX)}/#{image_name}:#{image_tag}"
puts
inspect_args = ["inspect", "--raw", image_uri.to_s]
if dry_run
puts "#{skopeo} #{inspect_args.join(" ")} --creds=#{user}:$HOMEBREW_GITHUB_PACKAGES_TOKEN"
else
inspect_args << "--creds=#{user}:#{token}"
inspect_result = system_command(skopeo, print_stderr: false, args: inspect_args)
# Order here is important.
if !inspect_result.status.success? && !inspect_result.stderr.match?(/(name|manifest) unknown/)
# We got an error and it was not about the tag or package being unknown.
if warn_on_error
opoo "#{image_uri} inspection returned an error, skipping upload!\n#{inspect_result.stderr}"
return
else
odie "#{image_uri} inspection returned an error!\n#{inspect_result.stderr}"
end
elsif keep_old
# If the tag doesn't exist, ignore `--keep-old`.
keep_old = false unless inspect_result.status.success?
# Otherwise, do nothing - the tag already existing is expected behaviour for --keep-old.
elsif inspect_result.status.success?
# The tag already exists and we are not passing `--keep-old`.
if warn_on_error
opoo "#{image_uri} already exists, skipping upload!"
return
else
odie "#{image_uri} already exists!"
end
end
end
[formula_name, org, repo, version, rebuild, version_rebuild, image_name, image_uri, keep_old]
end
sig {
params(
user: String, token: String, skopeo: Pathname, formula_full_name: String,
@@ -518,6 +375,149 @@ class GitHubPackages
end
end
private
IMAGE_CONFIG_SCHEMA_URI = "https://opencontainers.org/schema/image/config"
IMAGE_INDEX_SCHEMA_URI = "https://opencontainers.org/schema/image/index"
IMAGE_LAYOUT_SCHEMA_URI = "https://opencontainers.org/schema/image/layout"
IMAGE_MANIFEST_SCHEMA_URI = "https://opencontainers.org/schema/image/manifest"
GITHUB_PACKAGE_TYPE = "homebrew_bottle"
private_constant :IMAGE_CONFIG_SCHEMA_URI, :IMAGE_INDEX_SCHEMA_URI, :IMAGE_LAYOUT_SCHEMA_URI,
:IMAGE_MANIFEST_SCHEMA_URI, :GITHUB_PACKAGE_TYPE
sig { void }
def load_schemas!
schema_uri("content-descriptor",
"https://opencontainers.org/schema/image/content-descriptor.json")
schema_uri("defs", %w[
https://opencontainers.org/schema/defs.json
https://opencontainers.org/schema/descriptor/defs.json
https://opencontainers.org/schema/image/defs.json
https://opencontainers.org/schema/image/descriptor/defs.json
https://opencontainers.org/schema/image/index/defs.json
https://opencontainers.org/schema/image/manifest/defs.json
])
schema_uri("defs-descriptor", %w[
https://opencontainers.org/schema/descriptor.json
https://opencontainers.org/schema/defs-descriptor.json
https://opencontainers.org/schema/descriptor/defs-descriptor.json
https://opencontainers.org/schema/image/defs-descriptor.json
https://opencontainers.org/schema/image/descriptor/defs-descriptor.json
https://opencontainers.org/schema/image/index/defs-descriptor.json
https://opencontainers.org/schema/image/manifest/defs-descriptor.json
https://opencontainers.org/schema/index/defs-descriptor.json
])
schema_uri("config-schema", IMAGE_CONFIG_SCHEMA_URI)
schema_uri("image-index-schema", IMAGE_INDEX_SCHEMA_URI)
schema_uri("image-layout-schema", IMAGE_LAYOUT_SCHEMA_URI)
schema_uri("image-manifest-schema", IMAGE_MANIFEST_SCHEMA_URI)
end
sig { params(basename: String, uris: T.any(String, T::Array[String])).void }
def schema_uri(basename, uris)
# The current `main` version has an invalid JSON schema.
# Going forward, this should probably be pinned to tags.
# We currently use features newer than the last one (v1.0.2).
url = "https://raw.githubusercontent.com/opencontainers/image-spec/170393e57ed656f7f81c3070bfa8c3346eaa0a5a/schema/#{basename}.json"
out = Utils::Curl.curl_output(url).stdout
json = JSON.parse(out)
@schema_json ||= T.let({}, T.nilable(T::Hash[String, T::Hash[String, T.untyped]]))
Array(uris).each do |uri|
@schema_json[uri] = json
end
end
T::Sig::WithoutRuntime.sig { params(uri: URI::Generic).returns(T.nilable(T::Hash[String, T.untyped])) }
def schema_resolver(uri)
@schema_json&.fetch(uri.to_s.gsub(/#.*/, ""))
end
sig { params(schema_uri: String, json: T::Hash[T.any(String, Symbol), T.untyped]).void }
def validate_schema!(schema_uri, json)
schema = JSONSchemer.schema(@schema_json&.fetch(schema_uri), ref_resolver: method(:schema_resolver))
json = json.deep_stringify_keys
return if schema.valid?(json)
puts
ofail "#{Formatter.url(schema_uri)} JSON schema validation failed!"
oh1 "Errors"
puts schema.validate(json).to_a.inspect
oh1 "JSON"
puts json.inspect
exit 1
end
sig { params(user: String, token: String, skopeo: Pathname, image_uri: String, root: Pathname, dry_run: T::Boolean).void }
def download(user, token, skopeo, image_uri, root, dry_run:)
puts
args = ["copy", "--all", image_uri.to_s, "oci:#{root}"]
if dry_run
puts "#{skopeo} #{args.join(" ")} --src-creds=#{user}:$HOMEBREW_GITHUB_PACKAGES_TOKEN"
else
args << "--src-creds=#{user}:#{token}"
system_command!(skopeo, verbose: true, print_stdout: true, args:)
end
end
sig {
params(
user: String, token: String, skopeo: Pathname, _formula_full_name: String,
bottle_hash: T::Hash[String, T.untyped], keep_old: T::Boolean, dry_run: T::Boolean, warn_on_error: T::Boolean
).returns(
T.nilable([String, String, String, Version, Integer, String, String, String, T::Boolean]),
)
}
def preupload_check(user, token, skopeo, _formula_full_name, bottle_hash, keep_old:, dry_run:, warn_on_error:)
formula_name = bottle_hash["formula"]["name"]
_, org, repo, = *bottle_hash["bottle"]["root_url"].match(URL_REGEX)
repo = "homebrew-#{repo}" unless repo.start_with?("homebrew-")
version = Version.new(bottle_hash["formula"]["pkg_version"])
rebuild = bottle_hash["bottle"]["rebuild"].to_i
version_rebuild = GitHubPackages.version_rebuild(version, rebuild)
image_name = GitHubPackages.image_formula_name(formula_name)
image_tag = GitHubPackages.image_version_rebuild(version_rebuild)
image_uri = "#{GitHubPackages.root_url(org, repo, DOCKER_PREFIX)}/#{image_name}:#{image_tag}"
puts
inspect_args = ["inspect", "--raw", image_uri.to_s]
if dry_run
puts "#{skopeo} #{inspect_args.join(" ")} --creds=#{user}:$HOMEBREW_GITHUB_PACKAGES_TOKEN"
else
inspect_args << "--creds=#{user}:#{token}"
inspect_result = system_command(skopeo, print_stderr: false, args: inspect_args)
# Order here is important.
if !inspect_result.status.success? && !inspect_result.stderr.match?(/(name|manifest) unknown/)
# We got an error and it was not about the tag or package being unknown.
if warn_on_error
opoo "#{image_uri} inspection returned an error, skipping upload!\n#{inspect_result.stderr}"
return
else
odie "#{image_uri} inspection returned an error!\n#{inspect_result.stderr}"
end
elsif keep_old
# If the tag doesn't exist, ignore `--keep-old`.
keep_old = false unless inspect_result.status.success?
# Otherwise, do nothing - the tag already existing is expected behaviour for --keep-old.
elsif inspect_result.status.success?
# The tag already exists and we are not passing `--keep-old`.
if warn_on_error
opoo "#{image_uri} already exists, skipping upload!"
return
else
odie "#{image_uri} already exists!"
end
end
end
[formula_name, org, repo, version, rebuild, version_rebuild, image_name, image_uri, keep_old]
end
sig { params(root: Pathname).returns([String, Integer]) }
def write_image_layout(root)
image_layout = { imageLayoutVersion: "1.0.0" }
+80 -80
View File
@@ -108,86 +108,6 @@ class GitHubRunnerMatrix
end
end
private
# ARM macOS timeout, keep this under 1/2 of GitHub's job execution time limit for self-hosted runners.
# https://docs.github.com/en/actions/hosting-your-own-runners/managing-self-hosted-runners/about-self-hosted-runners#usage-limits
GITHUB_ACTIONS_LONG_TIMEOUT = 2160 # 36 hours
GITHUB_ACTIONS_SHORT_TIMEOUT = 60
private_constant :GITHUB_ACTIONS_LONG_TIMEOUT, :GITHUB_ACTIONS_SHORT_TIMEOUT
sig { params(arch: Symbol, self_hosted: T::Boolean).returns(LinuxRunnerSpec) }
def linux_runner_spec(arch, self_hosted:)
linux_runner = case arch
when :arm64 then self_hosted ? "linux-arm64#{ephemeral_suffix}" : OS::LINUX_CI_ARM_RUNNER
when :x86_64 then self_hosted ? "linux-x86_64#{ephemeral_suffix}" : "ubuntu-latest"
else raise "Unknown Linux architecture: #{arch}"
end
unless self_hosted
container = {
image: "ghcr.io/homebrew/brew:main",
options: "--user linuxbrew --env HOMEBREW_SANDBOX_LINUX_LANDLOCK=1",
}
workdir = "/github/home"
end
LinuxRunnerSpec.new(
name: "Linux #{arch}",
runner: linux_runner,
container:,
workdir:,
timeout: GITHUB_ACTIONS_LONG_TIMEOUT,
cleanup: false,
)
end
VALID_PLATFORMS = [:macos, :linux].freeze
VALID_ARCHES = [:arm64, :x86_64].freeze
private_constant :VALID_PLATFORMS, :VALID_ARCHES
sig {
params(
platform: Symbol,
arch: Symbol,
spec: RunnerSpec,
macos_version: T.nilable(MacOSVersion),
).returns(GitHubRunner)
}
def create_runner(platform, arch, spec, macos_version = nil)
raise "Unexpected platform: #{platform}" if VALID_PLATFORMS.exclude?(platform)
raise "Unexpected arch: #{arch}" if VALID_ARCHES.exclude?(arch)
runner = GitHubRunner.new(platform:, arch:, spec:, macos_version:)
runner.spec.testing_formulae += testable_formulae(runner)
runner.active = active_runner?(runner)
runner.freeze
end
sig { params(macos_version: MacOSVersion).returns(T::Boolean) }
def runner_enabled?(macos_version)
macos_version.between?(OLDEST_HOMEBREW_CORE_MACOS_RUNNER, NEWEST_HOMEBREW_CORE_MACOS_RUNNER)
end
sig { returns(String) }
def ephemeral_suffix
@ephemeral_suffix ||= T.let(begin
suffix = "-#{@github_run_id}"
suffix << "-deps" if @dependent_matrix
suffix << "-long" if @runner_timeout == GITHUB_ACTIONS_LONG_TIMEOUT
suffix.freeze
end, T.nilable(String))
end
NEWEST_GITHUB_ACTIONS_INTEL_MACOS_RUNNER = :ventura
OLDEST_GITHUB_ACTIONS_INTEL_MACOS_RUNNER = :ventura
NEWEST_GITHUB_ACTIONS_ARM_MACOS_RUNNER = :tahoe
OLDEST_GITHUB_ACTIONS_ARM_MACOS_RUNNER = :sonoma
GITHUB_ACTIONS_RUNNER_TIMEOUT = 360
private_constant :NEWEST_GITHUB_ACTIONS_INTEL_MACOS_RUNNER, :OLDEST_GITHUB_ACTIONS_INTEL_MACOS_RUNNER,
:NEWEST_GITHUB_ACTIONS_ARM_MACOS_RUNNER, :OLDEST_GITHUB_ACTIONS_ARM_MACOS_RUNNER,
:GITHUB_ACTIONS_RUNNER_TIMEOUT
sig { void }
def generate_runners!
return if @runners.present?
@@ -284,6 +204,86 @@ class GitHubRunnerMatrix
@runners.freeze
end
private
# ARM macOS timeout, keep this under 1/2 of GitHub's job execution time limit for self-hosted runners.
# https://docs.github.com/en/actions/hosting-your-own-runners/managing-self-hosted-runners/about-self-hosted-runners#usage-limits
GITHUB_ACTIONS_LONG_TIMEOUT = 2160 # 36 hours
GITHUB_ACTIONS_SHORT_TIMEOUT = 60
private_constant :GITHUB_ACTIONS_LONG_TIMEOUT, :GITHUB_ACTIONS_SHORT_TIMEOUT
sig { params(arch: Symbol, self_hosted: T::Boolean).returns(LinuxRunnerSpec) }
def linux_runner_spec(arch, self_hosted:)
linux_runner = case arch
when :arm64 then self_hosted ? "linux-arm64#{ephemeral_suffix}" : OS::LINUX_CI_ARM_RUNNER
when :x86_64 then self_hosted ? "linux-x86_64#{ephemeral_suffix}" : "ubuntu-latest"
else raise "Unknown Linux architecture: #{arch}"
end
unless self_hosted
container = {
image: "ghcr.io/homebrew/brew:main",
options: "--user linuxbrew --env HOMEBREW_SANDBOX_LINUX_LANDLOCK=1",
}
workdir = "/github/home"
end
LinuxRunnerSpec.new(
name: "Linux #{arch}",
runner: linux_runner,
container:,
workdir:,
timeout: GITHUB_ACTIONS_LONG_TIMEOUT,
cleanup: false,
)
end
VALID_PLATFORMS = [:macos, :linux].freeze
VALID_ARCHES = [:arm64, :x86_64].freeze
private_constant :VALID_PLATFORMS, :VALID_ARCHES
sig {
params(
platform: Symbol,
arch: Symbol,
spec: RunnerSpec,
macos_version: T.nilable(MacOSVersion),
).returns(GitHubRunner)
}
def create_runner(platform, arch, spec, macos_version = nil)
raise "Unexpected platform: #{platform}" if VALID_PLATFORMS.exclude?(platform)
raise "Unexpected arch: #{arch}" if VALID_ARCHES.exclude?(arch)
runner = GitHubRunner.new(platform:, arch:, spec:, macos_version:)
runner.spec.testing_formulae += testable_formulae(runner)
runner.active = active_runner?(runner)
runner.freeze
end
sig { params(macos_version: MacOSVersion).returns(T::Boolean) }
def runner_enabled?(macos_version)
macos_version.between?(OLDEST_HOMEBREW_CORE_MACOS_RUNNER, NEWEST_HOMEBREW_CORE_MACOS_RUNNER)
end
sig { returns(String) }
def ephemeral_suffix
@ephemeral_suffix ||= T.let(begin
suffix = "-#{@github_run_id}"
suffix << "-deps" if @dependent_matrix
suffix << "-long" if @runner_timeout == GITHUB_ACTIONS_LONG_TIMEOUT
suffix.freeze
end, T.nilable(String))
end
NEWEST_GITHUB_ACTIONS_INTEL_MACOS_RUNNER = :ventura
OLDEST_GITHUB_ACTIONS_INTEL_MACOS_RUNNER = :ventura
NEWEST_GITHUB_ACTIONS_ARM_MACOS_RUNNER = :tahoe
OLDEST_GITHUB_ACTIONS_ARM_MACOS_RUNNER = :sonoma
GITHUB_ACTIONS_RUNNER_TIMEOUT = 360
private_constant :NEWEST_GITHUB_ACTIONS_INTEL_MACOS_RUNNER, :OLDEST_GITHUB_ACTIONS_INTEL_MACOS_RUNNER,
:NEWEST_GITHUB_ACTIONS_ARM_MACOS_RUNNER, :OLDEST_GITHUB_ACTIONS_ARM_MACOS_RUNNER,
:GITHUB_ACTIONS_RUNNER_TIMEOUT
sig { params(runner: GitHubRunner).returns(T::Array[String]) }
def testable_formulae(runner)
formulae = if @dependent_matrix
+10 -10
View File
@@ -720,6 +720,16 @@ module Homebrew
ask_input(action:)
end
sig { params(all_fatal: T::Boolean).void }
def perform_preinstall_checks(all_fatal: false)
check_prefix
check_cpu
attempt_directory_creation
Diagnostic.checks(:supported_configuration_checks, fatal: all_fatal)
Diagnostic.checks(:preinstall_checks, fatal: false)
Diagnostic.checks(:fatal_preinstall_checks)
end
private
sig { params(action: String).returns(String) }
@@ -741,16 +751,6 @@ module Homebrew
.map { |k| Keg.new(k.resolved_path) }
end
sig { params(all_fatal: T::Boolean).void }
def perform_preinstall_checks(all_fatal: false)
check_prefix
check_cpu
attempt_directory_creation
Diagnostic.checks(:supported_configuration_checks, fatal: all_fatal)
Diagnostic.checks(:preinstall_checks, fatal: false)
Diagnostic.checks(:fatal_preinstall_checks)
end
sig { void }
def attempt_directory_creation
Keg.must_exist_directories.each do |dir|
+5
View File
@@ -12,6 +12,11 @@ module Language
module Node
extend ::Utils::Output::Mixin
class << self
sig { returns(T.nilable(T::Boolean)) }
attr_accessor :env_set
end
sig { returns(String) }
def self.npm_cache_config
"cache=#{HOMEBREW_CACHE}/npm_cache"
+4 -4
View File
@@ -34,13 +34,13 @@ module Homebrew
private_constant :UNSTABLE_VERSION_KEYWORDS
sig { params(strategy_class: T::Class[Strategic]).returns(String) }
private_class_method def self.livecheck_strategy_names(strategy_class)
def self.livecheck_strategy_names(strategy_class)
@livecheck_strategy_names ||= T.let({}, T.nilable(T::Hash[T::Class[Strategic], String]))
@livecheck_strategy_names[strategy_class] ||= Utils.demodulize(strategy_class.name)
end
sig { params(strategy_class: T::Class[Strategic]).returns(T::Array[Symbol]) }
private_class_method def self.livecheck_find_versions_parameters(strategy_class)
def self.livecheck_find_versions_parameters(strategy_class)
@livecheck_find_versions_parameters ||= T.let({}, T.nilable(T::Hash[T::Class[Strategic], T::Array[Symbol]]))
@livecheck_find_versions_parameters[strategy_class] ||=
(T::Utils.signature_for_method(strategy_class.method(:find_versions))&.parameters ||
@@ -1133,7 +1133,7 @@ module Homebrew
end
sig { params(package_or_resource: T.any(Formula, Cask::Cask)).returns(T.nilable(Integer)) }
private_class_method def self.formula_or_cask_last_updated_timestamp(package_or_resource)
def self.formula_or_cask_last_updated_timestamp(package_or_resource)
tap = package_or_resource.tap
return if tap.nil?
return unless tap.git?
@@ -1261,7 +1261,7 @@ module Homebrew
end
sig { params(package_or_resource: T.any(Formula, Cask::Cask), days: Integer).returns(T::Boolean) }
private_class_method def self.throttle_interval_elapsed?(package_or_resource, days)
def self.throttle_interval_elapsed?(package_or_resource, days)
return false if days <= 0
last_updated_timestamp = formula_or_cask_last_updated_timestamp(package_or_resource)
@@ -32,6 +32,11 @@ module Homebrew
# Used to cache processed URLs, to avoid duplicating effort.
@processed_urls = T.let({}, T::Hash[String, String])
class << self
sig { params(processed_urls: T::Hash[String, String]).void }
attr_writer :processed_urls
end
# The priority of the strategy on an informal scale of 1 to 10 (from
# lowest to highest).
PRIORITY = 8
@@ -63,6 +63,11 @@ module Homebrew
# repeatedly.
@page_data = T.let({}, T::Hash[String, String])
class << self
sig { params(page_data: T::Hash[String, String]).void }
attr_writer :page_data
end
# Whether the strategy can be applied to the provided URL.
#
# @param url [String] the URL to match against
+6
View File
@@ -54,6 +54,12 @@ class MacOSVersion < Version
new(str)
end
sig { returns(T::Hash[T.untyped, T.nilable(Integer)]) }
attr_reader :comparison_cache
sig { returns(T.nilable(Symbol)) }
attr_reader :sym
sig { params(version: T.nilable(String)).void }
def initialize(version)
raise MacOSVersion::Error, version unless /\A\d{2,}(?:\.\d+){0,2}\z/.match?(version)
+5
View File
@@ -19,6 +19,11 @@ module OS
/system/bin/linker
].freeze
class << self
sig { params(system_ld_so: T.nilable(::Pathname)).returns(T.nilable(::Pathname)) }
attr_writer :system_ld_so
end
# The path to the system's dynamic linker or `nil` if not found
sig { returns(T.nilable(::Pathname)) }
def self.system_ld_so
+5
View File
@@ -10,6 +10,11 @@ module OS
SOVERSION = 6
SONAME = T.let("libstdc++.so.#{SOVERSION}".freeze, String)
class << self
sig { params(system_version: T.nilable(Version)).returns(T.nilable(Version)) }
attr_writer :system_version
end
sig { returns(T::Boolean) }
def self.below_ci_version?
system_version < LINUX_LIBSTDCXX_CI_VERSION
+2 -2
View File
@@ -116,8 +116,6 @@ module Homebrew
nil
end
private
sig { params(keg: Keg).void }
def backup(keg)
keg.unlink
@@ -132,6 +130,8 @@ module Homebrew
end
end
private
sig { params(keg: Keg, keg_was_linked: T::Boolean, verbose: T::Boolean).void }
def restore_backup(keg, keg_was_linked, verbose:)
path = backup_path(keg)
+3
View File
@@ -357,6 +357,9 @@ class Resource
sig { returns(Bottle) }
attr_reader :bottle
sig { params(manifest_annotations: T.nilable(T::Hash[String, String])).void }
attr_writer :manifest_annotations
sig { params(bottle: Bottle).void }
def initialize(bottle)
super("#{bottle.name}_bottle_manifest")
+2
View File
@@ -14,6 +14,8 @@ require_relative "io_read"
require_relative "move_to_extend_os"
require_relative "negate_include"
require_relative "no_fileutils_rmrf"
require_relative "no_instance_variable_access_in_tests"
require_relative "no_send_in_tests"
require_relative "non_public_api_usage"
require_relative "presence"
require_relative "public_api_cookbook"
@@ -0,0 +1,34 @@
# typed: strict
# frozen_string_literal: true
module RuboCop
module Cop
module Homebrew
# Flags `instance_variable_get`/`instance_variable_set` in tests. Tests should read
# and write object state through public accessors: add a public `attr_reader`/
# `attr_writer` (or use an existing accessor) on the class instead of reaching into
# its instance variables.
#
# ### Example
#
# ```ruby
# # bad
# formula.instance_variable_set(:@tap, CoreTap.instance)
#
# # good (with a public `attr_writer :tap`)
# formula.tap = CoreTap.instance
# ```
class NoInstanceVariableAccessInTests < Base
MSG = "Use a public `attr_reader`/`attr_writer` (or an existing accessor) instead of " \
"`%<method>s` in tests."
RESTRICT_ON_SEND = [:instance_variable_get, :instance_variable_set].freeze
sig { params(node: RuboCop::AST::SendNode).void }
def on_send(node)
add_offense(node.loc.selector, message: format(MSG, method: node.method_name))
end
alias on_csend on_send
end
end
end
end
@@ -0,0 +1,73 @@
# typed: strict
# frozen_string_literal: true
module RuboCop
module Cop
module Homebrew
# Flags `send`-family dispatch in tests. Tests should exercise methods the way real
# callers do: a private method poked via `send` should be made public and called
# directly instead.
#
# - `send`/`__send__` are always flagged: with a static method name the call can be
# written directly (after making the method public if needed); with a dynamic one
# it must go through `public_send` so it cannot bypass method visibility.
# - `public_send` is flagged only when the method name is a literal that could have
# been written as a direct call. A dynamic name (`public_send(method_name)`,
# `public_send(:"#{artifact_dsl_key}_phase")`) is the one legitimate use:
# parameterised dispatch to public API. A literal name with no direct call syntax
# (e.g. `:"gcc-9"`) is also allowed, as no direct call can spell it.
#
# ### Example
#
# ```ruby
# # bad
# formula.send(:active_spec)
#
# # good (with `active_spec` made public)
# formula.active_spec
#
# # good (dynamic dispatch to public API in a parameterised example)
# subject.public_send(:"#{artifact_dsl_key}_phase")
# ```
class NoSendInTests < Base
MSG_SEND = "Make the method public and call it directly instead of using `%<method>s` in tests."
MSG_SEND_DYNAMIC = "Use `public_send` instead of `%<method>s` in tests; " \
"`%<method>s` bypasses method visibility."
MSG_PUBLIC_SEND = "Call the method directly instead of using `public_send` with a static method name."
RESTRICT_ON_SEND = [:send, :__send__, :public_send].freeze
# A literal method name that direct call syntax can spell, including setters
# (`public_send(:foo=, value)` can be written `receiver.foo = value`).
DIRECTLY_CALLABLE_NAME = /\A[a-zA-Z_][a-zA-Z0-9_]*[?!=]?\z/
sig { params(node: RuboCop::AST::SendNode).void }
def on_send(node)
directly_callable = directly_callable_name?(node.first_argument)
message = if node.method_name == :public_send
return unless directly_callable
MSG_PUBLIC_SEND
elsif directly_callable
format(MSG_SEND, method: node.method_name)
else
format(MSG_SEND_DYNAMIC, method: node.method_name)
end
add_offense(node.loc.selector, message:)
end
alias on_csend on_send
private
sig { params(argument: T.nilable(RuboCop::AST::Node)).returns(T::Boolean) }
def directly_callable_name?(argument)
return false unless argument
return false if !argument.sym_type? && !argument.str_type?
argument.children.first.to_s.match?(DIRECTLY_CALLABLE_NAME)
end
end
end
end
end
+10 -10
View File
@@ -595,11 +595,19 @@ class Sandbox
SandboxPathFilter.new(path: filter_path, type:)
end
private
sig { returns(SandboxProfile) }
attr_reader :profile
sig { params(controller: IO).void }
def copy_pty_output(controller)
controller.each_char { |c| print(c) }
rescue Errno::EIO
# Linux marks a PTY as an I/O error when its peer closes, so treat this as EOF:
# https://github.com/torvalds/linux/blob/master/drivers/tty/pty.c
end
private
sig { returns(T::Boolean) }
attr_reader :failed
@@ -630,14 +638,6 @@ class Sandbox
sig { void }
def apply_sandbox; end
sig { params(controller: IO).void }
def copy_pty_output(controller)
controller.each_char { |c| print(c) }
rescue Errno::EIO
# Linux marks a PTY as an I/O error when its peer closes, so treat this as EOF:
# https://github.com/torvalds/linux/blob/master/drivers/tty/pty.c
end
sig { void }
def record_sandbox_log; end
-1
View File
@@ -613,7 +613,6 @@ class Tap
SystemCommand.run!("git", args:, chdir:, env: { "GIT_TERMINAL_PROMPT" => "0" }, print_stderr: true)
end
private :git_command!
# Install this {Tap}.
#
@@ -58,8 +58,8 @@ RSpec.describe Homebrew::API::CaskStruct do
)
Homebrew::API::CaskStruct::PREDICATES.each do |predicate|
expect(struct.send(:"#{predicate}?")).to be(false),
"expected #{predicate}? to default to false"
expect(struct.public_send(:"#{predicate}?")).to be(false),
"expected #{predicate}? to default to false"
end
end
@@ -76,8 +76,8 @@ RSpec.describe Homebrew::API::CaskStruct do
)
Homebrew::API::CaskStruct::PREDICATES.each do |predicate|
expect(struct.send(:"#{predicate}?")).to be(true),
"expected #{predicate}? to be true"
expect(struct.public_send(:"#{predicate}?")).to be(true),
"expected #{predicate}? to be true"
end
end
end
@@ -232,7 +232,7 @@ RSpec.describe Homebrew::API::CaskStruct do
struct = described_class.deserialize(hash)
Homebrew::API::CaskStruct::PREDICATES.each do |predicate|
expect(struct.send(:"#{predicate}?")).to be false
expect(struct.public_send(:"#{predicate}?")).to be false
end
end
@@ -255,7 +255,7 @@ RSpec.describe Homebrew::API::CaskStruct do
struct = described_class.deserialize(hash)
Homebrew::API::CaskStruct::PREDICATES.each do |predicate|
expect(struct.send(:"#{predicate}?")).to be true
expect(struct.public_send(:"#{predicate}?")).to be true
end
end
end
@@ -106,8 +106,8 @@ RSpec.describe Homebrew::API::FormulaStruct do
)
Homebrew::API::FormulaStruct::PREDICATES.each do |predicate|
expect(struct.send(:"#{predicate}?")).to be(false),
"expected #{predicate}? to default to false"
expect(struct.public_send(:"#{predicate}?")).to be(false),
"expected #{predicate}? to default to false"
end
end
@@ -126,8 +126,8 @@ RSpec.describe Homebrew::API::FormulaStruct do
)
Homebrew::API::FormulaStruct::PREDICATES.each do |predicate|
expect(struct.send(:"#{predicate}?")).to be(true),
"expected #{predicate}? to be true"
expect(struct.public_send(:"#{predicate}?")).to be(true),
"expected #{predicate}? to be true"
end
end
end
+4 -3
View File
@@ -7,14 +7,15 @@ RSpec.describe Homebrew::Bump do
describe "::redacted_url" do
it "masks env-token credentials embedded in a push URL" do
allow(GitHub::API).to receive(:credentials).and_return("ghp_secrettoken")
expect(described_class.send(:redacted_url,
"https://x-access-token:ghp_secrettoken@github.com/Homebrew/homebrew-core"))
expect(described_class.redacted_url(
"https://x-access-token:ghp_secrettoken@github.com/Homebrew/homebrew-core",
))
.to eq("https://x-access-token:******@github.com/Homebrew/homebrew-core")
end
it "leaves a credential-free URL unchanged" do
allow(GitHub::API).to receive(:credentials).and_return(nil)
expect(described_class.send(:redacted_url, "https://github.com/Homebrew/homebrew-core"))
expect(described_class.redacted_url("https://github.com/Homebrew/homebrew-core"))
.to eq("https://github.com/Homebrew/homebrew-core")
end
end
+8 -9
View File
@@ -189,20 +189,19 @@ RSpec.describe Homebrew::Bundle::Dsl do
end
it ".sanitize_brew_name" do
expect(described_class.send(:sanitize_brew_name, "homebrew/homebrew/foo")).to eql("foo")
expect(described_class.send(:sanitize_brew_name, "homebrew/homebrew-bar/foo")).to eql("homebrew/bar/foo")
expect(described_class.send(:sanitize_brew_name, "homebrew/bar/foo")).to eql("homebrew/bar/foo")
expect(described_class.send(:sanitize_brew_name, "foo")).to eql("foo")
expect(described_class.sanitize_brew_name("homebrew/homebrew/foo")).to eql("foo")
expect(described_class.sanitize_brew_name("homebrew/homebrew-bar/foo")).to eql("homebrew/bar/foo")
expect(described_class.sanitize_brew_name("homebrew/bar/foo")).to eql("homebrew/bar/foo")
expect(described_class.sanitize_brew_name("foo")).to eql("foo")
end
it ".sanitize_tap_name" do
expect(described_class.send(:sanitize_tap_name, "homebrew/homebrew-foo")).to eql("homebrew/foo")
expect(described_class.send(:sanitize_tap_name, "homebrew/foo")).to eql("homebrew/foo")
expect(described_class.sanitize_tap_name("homebrew/homebrew-foo")).to eql("homebrew/foo")
expect(described_class.sanitize_tap_name("homebrew/foo")).to eql("homebrew/foo")
end
it ".sanitize_cask_name" do
expect(described_class.send(:sanitize_cask_name,
"homebrew/cask-versions/adoptopenjdk8")).to eql("adoptopenjdk8")
expect(described_class.send(:sanitize_cask_name, "adoptopenjdk8")).to eql("adoptopenjdk8")
expect(described_class.sanitize_cask_name("homebrew/cask-versions/adoptopenjdk8")).to eql("adoptopenjdk8")
expect(described_class.sanitize_cask_name("adoptopenjdk8")).to eql("adoptopenjdk8")
end
end
@@ -280,7 +280,7 @@ RSpec.describe Homebrew::Bundle::Installer do
output = IO.pipe do |reader, writer|
allow(writer).to receive(:tty?).and_return(true)
parallel_installer.send(:write_output, "Installing alpha", stream: writer)
parallel_installer.write_output("Installing alpha", stream: writer)
writer.close
reader.read
@@ -293,7 +293,7 @@ RSpec.describe Homebrew::Bundle::Installer do
output = IO.pipe do |reader, writer|
allow(writer).to receive(:tty?).and_return(false)
parallel_installer.send(:write_output, "Installing alpha", stream: writer)
parallel_installer.write_output("Installing alpha", stream: writer)
writer.close
reader.read
@@ -417,7 +417,7 @@ RSpec.describe Homebrew::Bundle::Installer do
dependency_map = Homebrew::Bundle::ParallelInstaller.new(
entries,
jobs: 2, no_upgrade: false, verbose: false, force: false, quiet: true,
).send(:build_dependency_map, entries)
).build_dependency_map(entries)
expect(dependency_map.fetch("beta")).to eq(Set["alpha"])
end
@@ -440,7 +440,7 @@ RSpec.describe Homebrew::Bundle::Installer do
dependency_map = Homebrew::Bundle::ParallelInstaller.new(
entries,
jobs: 2, no_upgrade: false, verbose: false, force: false, quiet: true,
).send(:build_dependency_map, entries)
).build_dependency_map(entries)
expect(dependency_map.fetch("alpha")).to eq(Set["gh"])
end
+2 -2
View File
@@ -13,8 +13,8 @@ RSpec.describe Homebrew::Bundle::Skipper do
allow(ENV).to receive(:[]).with("HOMEBREW_BUNDLE_BREW_SKIP").and_return("mysql")
allow(ENV).to receive(:[]).with("HOMEBREW_BUNDLE_TAP_SKIP").and_return("org/repo")
allow(Formatter).to receive(:warning)
skipper.instance_variable_set(:@skipped_entries, nil)
skipper.instance_variable_set(:@failed_taps, nil)
skipper.skipped_entries = nil
skipper.failed_taps = nil
end
describe ".skip?" do
+1 -1
View File
@@ -99,7 +99,7 @@ RSpec.describe CacheStoreDatabase do
context "without an open database" do
before do
sample_db.instance_variable_set(:@db, nil)
sample_db.db = nil
end
it "does not raise an error when `close` is called on the database" do
@@ -21,7 +21,7 @@ RSpec.describe Cask::Artifact::AbstractUninstall, :cask do
it "skips relative paths" do
expect do
expect(artifact.send(:each_resolved_path, :delete, ["relative/path"]).to_a).to be_empty
expect(artifact.each_resolved_path(:delete, ["relative/path"]).to_a).to be_empty
end.to output(%r{Skipping delete for relative path 'relative/path'\.}).to_stderr
end
@@ -36,7 +36,7 @@ RSpec.describe Cask::Artifact::AbstractUninstall, :cask do
tmpdir/"nested/./#{valid_path.basename}",
].each do |invalid_path|
expect do
expect(artifact.send(:each_resolved_path, :delete, [invalid_path.to_s]).to_a).to be_empty
expect(artifact.each_resolved_path(:delete, [invalid_path.to_s]).to_a).to be_empty
end.to output(
/Skipping delete for path with relative segments '#{Regexp.escape(invalid_path.to_s)}'\./,
).to_stderr
@@ -49,7 +49,7 @@ RSpec.describe Cask::Artifact::AbstractUninstall, :cask do
invalid_path = "~/../each_resolved_path_#{artifact_dsl_key}"
expect do
expect(artifact.send(:each_resolved_path, :delete, [invalid_path]).to_a).to be_empty
expect(artifact.each_resolved_path(:delete, [invalid_path]).to_a).to be_empty
end.to output(
/Skipping delete for path with relative segments '#{Regexp.escape(invalid_path)}'\./,
).to_stderr
@@ -66,7 +66,7 @@ RSpec.describe Cask::Artifact::AbstractUninstall, :cask do
allow(artifact).to receive(:undeletable?) { |target| target == undeletable_path }
expect do
expect(artifact.send(:each_resolved_path, :delete, ["#{glob_dir}/*.plist"]).to_a)
expect(artifact.each_resolved_path(:delete, ["#{glob_dir}/*.plist"]).to_a)
.to eq([["#{glob_dir}/*.plist", [safe_path]]])
end.to output(
/Skipping delete for undeletable path '#{Regexp.escape(undeletable_path.to_s)}'\./,
@@ -81,7 +81,7 @@ RSpec.describe Cask::Artifact::AbstractUninstall, :cask do
allow(MacOS).to receive(:version).and_return(MacOSVersion.from_symbol(:ventura))
expect do
artifact.send(:each_resolved_path, :delete, ["/tmp/each_resolved_path_#{artifact_dsl_key}"]).to_a
artifact.each_resolved_path(:delete, ["/tmp/each_resolved_path_#{artifact_dsl_key}"]).to_a
end.to raise_error(SystemExit)
.and output(/Full Disk Access/).to_stderr
end
@@ -315,7 +315,7 @@ RSpec.describe Cask::Artifact::App, :cask do
allow(MacOS).to receive(:version).and_return(MacOSVersion.from_symbol(:sonoma))
expect(app.send(:backup_copy_args, target_path, source_path)).to eq(["-c", "-pR", target_path, source_path])
expect(app.backup_copy_args(target_path, source_path)).to eq(["-c", "-pR", target_path, source_path])
end
it "uses portable copy arguments on older macOS versions", :needs_macos do
@@ -323,7 +323,7 @@ RSpec.describe Cask::Artifact::App, :cask do
allow(MacOS).to receive(:version).and_return(MacOSVersion.from_symbol(:ventura))
expect(app.send(:backup_copy_args, target_path, source_path)).to eq(["-pR", target_path, source_path])
expect(app.backup_copy_args(target_path, source_path)).to eq(["-pR", target_path, source_path])
end
it "uses portable copy arguments across filesystems", :needs_macos do
@@ -335,7 +335,7 @@ RSpec.describe Cask::Artifact::App, :cask do
allow(source_path).to receive(:dirname).and_return(source_dir)
allow(source_dir).to receive(:stat).and_return(instance_double(File::Stat, dev: 2))
expect(app.send(:backup_copy_args, target_path, source_path)).to eq(["-pR", target_path, source_path])
expect(app.backup_copy_args(target_path, source_path)).to eq(["-pR", target_path, source_path])
end
end
@@ -5,13 +5,11 @@ RSpec.describe Cask::Artifact::GeneratedCompletion, :cask do
let(:staged_path) { Pathname(Dir.mktmpdir) }
let(:cask) do
tmp_staged = staged_path
Cask::Cask.new("test-generated-completion") do
version "1.0"
sha256 :no_check
url "file:///dev/null"
generate_completions_from_executable "bin/foo", "completions"
instance_variable_set(:@staged_path, tmp_staged)
end
end
@@ -20,6 +18,7 @@ RSpec.describe Cask::Artifact::GeneratedCompletion, :cask do
let(:fish_dir) { cask.config.fish_completion }
before do
allow(cask).to receive(:staged_path).and_return(staged_path)
(staged_path/"bin").mkpath
(staged_path/"bin/foo").write("#!/bin/sh\necho \"$SHELL completion\"")
(staged_path/"bin/foo").chmod(0755)
@@ -134,14 +133,12 @@ RSpec.describe Cask::Artifact::GeneratedCompletion, :cask do
context "with specific shells and format" do
let(:cask) do
tmp_staged = staged_path
Cask::Cask.new("test-generated-completion") do
version "1.0"
sha256 :no_check
url "file:///dev/null"
generate_completions_from_executable "bin/foo", "completions",
shells: [:zsh], shell_parameter_format: :arg, base_name: "bar"
instance_variable_set(:@staged_path, tmp_staged)
end
end
@@ -175,14 +172,12 @@ RSpec.describe Cask::Artifact::GeneratedCompletion, :cask do
context "with string shells" do
let(:cask) do
tmp_staged = staged_path
Cask::Cask.new("test-generated-completion") do
version "1.0"
sha256 :no_check
url "file:///dev/null"
generate_completions_from_executable "bin/foo", "completions",
shells: %w[bash zsh fish pwsh]
instance_variable_set(:@staged_path, tmp_staged)
end
end
@@ -29,7 +29,7 @@ RSpec.describe Cask::Artifact::Relocated, :cask do
expect(command).not_to receive(:run)
expect(command).not_to receive(:run!)
artifact.send(:add_altname_metadata, file, altname, command: command)
artifact.add_altname_metadata(file, altname, command: command)
end
end
@@ -46,7 +46,7 @@ RSpec.describe Cask::Artifact::Relocated, :cask do
print_stderr: false)
expect(command).to receive(:run!).twice
artifact.send(:add_altname_metadata, file, altname, command: command)
artifact.add_altname_metadata(file, altname, command: command)
end
end
end
@@ -186,9 +186,8 @@ RSpec.shared_examples "#uninstall_phase or #zap_phase" do
.with("/bin/launchctl", args: ["list"])
.and_return(instance_double(SystemCommand::Result, stdout: launchctl_list))
expect(subject.send(:find_launchctl_with_wildcard,
"my.fancy.package.service.*")).to eq(["my.fancy.package.service.12345",
"my.fancy.package.service.test"])
expect(subject.find_launchctl_with_wildcard("my.fancy.package.service.*"))
.to eq(["my.fancy.package.service.12345", "my.fancy.package.service.test"])
end
end
@@ -149,7 +149,7 @@ RSpec.describe Cask::Artifact::Uninstall, :cask do
allow(artifact).to receive(:quit).with(bundle_id)
.and_return(instance_double(SystemCommand::Result, success?: true))
artifact.send(:uninstall_quit, bundle_id, upgrade: true, command: fake_system_command)
artifact.uninstall_quit(bundle_id, upgrade: true, command: fake_system_command)
expect(artifact.bundle_ids_to_reopen).to eq [bundle_id]
end
@@ -159,7 +159,7 @@ RSpec.describe Cask::Artifact::Uninstall, :cask do
allow(artifact).to receive(:quit).with(bundle_id)
.and_return(instance_double(SystemCommand::Result, success?: true))
artifact.send(:uninstall_quit, bundle_id, upgrade: false, command: fake_system_command)
artifact.uninstall_quit(bundle_id, upgrade: false, command: fake_system_command)
expect(artifact.bundle_ids_to_reopen).to be_empty
end
@@ -171,7 +171,7 @@ RSpec.describe Cask::Artifact::Uninstall, :cask do
allow(Timeout).to receive(:timeout).and_raise(Timeout::Error)
expect do
artifact.send(:uninstall_quit, bundle_id, upgrade: true, command: fake_system_command)
artifact.uninstall_quit(bundle_id, upgrade: true, command: fake_system_command)
end.to output(/did not quit/).to_stderr
expect(artifact.bundle_ids_to_reopen).to be_empty
+4 -4
View File
@@ -542,7 +542,7 @@ RSpec.describe Cask::Audit, :cask do
allow(Cask::Quarantine).to receive(:available?).and_return(false)
expect(Cask::Quarantine).not_to receive(:detect)
audit.send(:extract_artifacts)
audit.extract_artifacts
end
end
@@ -555,9 +555,9 @@ RSpec.describe Cask::Audit, :cask do
let(:cask_token) { "basic-cask" }
it "returns existing `@livecheck_result` value" do
audit.instance_variable_set(:@livecheck_result, :auto_detected)
audit.livecheck_result = :auto_detected
expect(run).not_to error_with(message)
audit.instance_variable_set(:@livecheck_result, nil)
audit.livecheck_result = nil
end
end
@@ -1237,7 +1237,7 @@ RSpec.describe Cask::Audit, :cask do
end
it "normalizes 10.16.0 minimum macOS to Big Sur" do
expect(audit.send(:normalize_min_os, "10.16.0")).to eq(MacOSVersion.from_symbol(:big_sur))
expect(audit.normalize_min_os("10.16.0")).to eq(MacOSVersion.from_symbol(:big_sur))
end
end
+7 -11
View File
@@ -39,35 +39,31 @@ RSpec.describe Cask::Auditor, :cask do
it "returns true if @any_named_args is true" do
auditor_obj = auditor.new(cask, any_named_args: true)
expect(auditor_obj.send(:output_summary?)).to be(true)
expect(auditor_obj.output_summary?).to be(true)
end
it "returns true if @audit_strict is true" do
auditor_obj = auditor.new(cask, audit_strict: true)
expect(auditor_obj.send(:output_summary?)).to be(true)
expect(auditor_obj.output_summary?).to be(true)
end
it "returns false if the audit argument is nil" do
auditor_obj = auditor.new(cask)
expect(auditor_obj.send(:output_summary?)).to be(false)
expect(auditor_obj.send(:output_summary?, nil)).to be(false)
expect(auditor_obj.output_summary?).to be(false)
expect(auditor_obj.output_summary?(nil)).to be(false)
end
it "returns false if there are no audit errors" do
auditor_obj = auditor.new(cask)
audit = Cask::Audit.new(cask)
expect(auditor_obj.send(:output_summary?, audit)).to be(false)
expect(auditor_obj.output_summary?(audit)).to be(false)
end
it "returns true if there are audit errors" do
auditor_obj = auditor.new(cask)
audit = Cask::Audit.new(cask)
audit.instance_variable_set(:@errors, [{
message: nil,
location: nil,
corrected: false,
}])
expect(auditor_obj.send(:output_summary?, audit)).to be(true)
audit.add_error(nil)
expect(auditor_obj.output_summary?(audit)).to be(true)
end
end
end
@@ -425,7 +425,7 @@ RSpec.describe Cask::CaskLoader, :cask do
it "raises CaskUnreadableError when loaded from installed caskfile" do
loader = Cask::CaskLoader::FromPathLoader.new(cask_file)
loader.instance_variable_set(:@from_installed_caskfile, true)
loader.from_installed_caskfile = true
expect { loader.load(config: nil) }.to raise_error(Cask::CaskUnreadableError, /appcast/)
end
end
@@ -491,7 +491,7 @@ RSpec.describe Cask::CaskLoader, :cask do
it "raises CaskUnreadableError when loaded from installed caskfile" do
loader = Cask::CaskLoader::FromPathLoader.new(cask_file)
loader.instance_variable_set(:@from_installed_caskfile, true)
loader.from_installed_caskfile = true
expect { loader.load(config: nil) }.to raise_error(Cask::CaskUnreadableError, /Unknown key: :formula/)
end
end
+1 -1
View File
@@ -4,7 +4,7 @@
require "cask/caskroom"
RSpec.describe Cask::Caskroom do
before { described_class.instance_variable_set(:@expected_caskroom_group, nil) }
before { described_class.expected_caskroom_group = nil }
describe ".ensure_caskroom_exists" do
it "changes the group when sudo is unnecessary and the group is wrong" do
+1 -1
View File
@@ -3,7 +3,7 @@
RSpec.describe Cask::Download, :cask do
describe "#download_name" do
subject(:download_name) { described_class.new(cask).send(:download_name) }
subject(:download_name) { described_class.new(cask).download_name }
let(:token) { "example-cask" }
let(:full_token) { token }
@@ -73,7 +73,7 @@ RSpec.describe Cask::DSL::Version, :cask do
end
shared_examples "version expectations hash" do |method, hash|
subject { version.send(method) }
subject { version.public_send(method) }
include_examples "expectations hash", :raw_version,
{ :latest => "latest",
+1 -1
View File
@@ -736,7 +736,7 @@ RSpec.describe Cask::DSL, :cask, :no_api do
it "allows installer manual to be specified" do
installer = cask.artifacts.first
expect(installer.instance_variable_get(:@manual_install)).to be true
expect(installer.manual_install).to be true
expect(installer.path).to eq(Pathname("Caffeine.app"))
end
end
+4 -4
View File
@@ -921,7 +921,7 @@ RSpec.describe Cask::Installer, :cask do
allow(cask).to receive(:staged_path).and_return(staged_path)
installer = described_class.new(cask)
installer.send(:process_rename_operations)
installer.process_rename_operations
expect(staged_path / "Renamed App.app").to be_a_directory
expect(staged_path / "Original App.app").not_to exist
@@ -941,7 +941,7 @@ RSpec.describe Cask::Installer, :cask do
allow(cask).to receive(:staged_path).and_return(staged_path)
installer = described_class.new(cask)
installer.send(:process_rename_operations)
installer.process_rename_operations
expect(staged_path / "Final Name.app").to be_a_directory
expect(staged_path / "Original.app").not_to exist
@@ -961,7 +961,7 @@ RSpec.describe Cask::Installer, :cask do
allow(cask).to receive(:staged_path).and_return(staged_path)
installer = described_class.new(cask)
installer.send(:process_rename_operations)
installer.process_rename_operations
expect(staged_path / "Test App.pkg").to be_a_file
expect((staged_path / "Test App.pkg").read).to eq("test content")
@@ -982,7 +982,7 @@ RSpec.describe Cask::Installer, :cask do
installer = described_class.new(cask)
expect { installer.send(:process_rename_operations) }.not_to raise_error
expect { installer.process_rename_operations }.not_to raise_error
expect(staged_path / "Different.app").to be_a_directory
expect(staged_path / "Target.app").not_to exist
end
@@ -102,7 +102,7 @@ RSpec.describe Homebrew::Cmd::Bundle::CheckSubcommand, :no_api do
end
it "raises an error for an implicitly unlinked non-keg-only formula" do
Homebrew::Bundle::Brew.instance_variable_set(:@formulae_by_name, { "abc" => { link?: false } })
Homebrew::Bundle::Brew.formulae_by_name = { "abc" => { link?: false } }
allow_any_instance_of(Pathname).to receive(:read).and_return("brew 'abc'")
allow(Formula["abc"]).to receive(:linked?).and_return(false)
@@ -111,7 +111,7 @@ RSpec.describe Homebrew::Cmd::Bundle::CheckSubcommand, :no_api do
end
it "does not raise an error when live link status satisfies an implicit check" do
Homebrew::Bundle::Brew.instance_variable_set(:@formulae_by_name, { "abc" => { link?: false } })
Homebrew::Bundle::Brew.formulae_by_name = { "abc" => { link?: false } }
allow_any_instance_of(Pathname).to receive(:read).and_return("brew 'abc'")
allow(Formula["abc"]).to receive(:linked?).and_return(true)
@@ -130,7 +130,7 @@ RSpec.describe Homebrew::Cmd::Bundle::CheckSubcommand, :no_api do
end
it "outputs the implicit link status error" do
Homebrew::Bundle::Brew.instance_variable_set(:@formulae_by_name, { "abc" => { link?: true } })
Homebrew::Bundle::Brew.formulae_by_name = { "abc" => { link?: true } }
allow_any_instance_of(Pathname).to receive(:read).and_return("brew 'abc'")
allow(Formula["abc"]).to receive(:linked?).and_return(true)
+3 -3
View File
@@ -89,7 +89,7 @@ RSpec.describe Homebrew::Cmd::FetchCmd do
describe "#cask_downloads", :cask do
it "collects one download per distinct URL across all platforms" do
cmd = described_class.new(["--cask", "--all-platforms", "sha256-os"])
basenames = cmd.send(:cask_downloads, Cask::CaskLoader.load("sha256-os"))
basenames = cmd.cask_downloads(Cask::CaskLoader.load("sha256-os"))
.map { |download| File.basename(download.url.to_s) }
expect(basenames).to contain_exactly("caffeine-arm-darwin.zip", "caffeine-intel-darwin.zip",
"caffeine-arm-linux.zip", "caffeine-intel-linux.zip")
@@ -97,12 +97,12 @@ RSpec.describe Homebrew::Cmd::FetchCmd do
it "skips arches the cask's depends_on arch excludes" do
cmd = described_class.new(["--cask", "--os=macos", "--arch=intel", "depends-on-arch-arm64"])
expect(cmd.send(:cask_downloads, Cask::CaskLoader.load("depends-on-arch-arm64"))).to be_empty
expect(cmd.cask_downloads(Cask::CaskLoader.load("depends-on-arch-arm64"))).to be_empty
end
it "collapses to a single download for a cask without on_system blocks" do
cmd = described_class.new(["--cask", "--all-platforms", "local-caffeine"])
expect(cmd.send(:cask_downloads, Cask::CaskLoader.load("local-caffeine")).length).to eq(1)
expect(cmd.cask_downloads(Cask::CaskLoader.load("local-caffeine")).length).to eq(1)
end
end
end
+51 -51
View File
@@ -256,7 +256,7 @@ RSpec.describe Homebrew::Cmd::Info do
Formula from https://example.com/testball.rb
Not installed
EOS
expect { info.send(:info_formula_summary, formula) }
expect { info.info_formula_summary(formula) }
.to output(expected_output).to_stdout
.and not_to_output.to_stderr
end
@@ -290,7 +290,7 @@ RSpec.describe Homebrew::Cmd::Info do
allow(info).to receive(:github_info).with(formula).and_return("https://example.com/testball.rb")
allow(formula).to receive_messages(core_formula?: false, missing_library_linkage: [[], Set.new])
expect { info.send(:info_formula, formula) }
expect { info.info_formula(formula) }
.to output(/Installs from source: yes/).to_stdout
.and not_to_output(/Metadata/).to_stdout
.and not_to_output(/supports macOS and Linux/).to_stdout
@@ -308,7 +308,7 @@ RSpec.describe Homebrew::Cmd::Info do
allow(other).to receive(:full_name).and_return("someuser/tap/other")
allow(Formulary).to receive(:factory).with("other").and_return(other)
expect { info.send(:info_formula, formula) }
expect { info.info_formula(formula) }
.to output(%r{Conflicts with:\n someuser/tap/other}).to_stdout
end
@@ -321,7 +321,7 @@ RSpec.describe Homebrew::Cmd::Info do
allow(info).to receive(:github_info).with(formula).and_return("https://example.com/testball.rb")
allow(Formulary).to receive(:factory).with("testball").and_return(formula)
expect { info.send(:info_formula, formula) }
expect { info.info_formula(formula) }
.not_to output(/Conflicts with:/).to_stdout
end
@@ -338,7 +338,7 @@ RSpec.describe Homebrew::Cmd::Info do
allow(info).to receive(:github_info).with(formula).and_return("https://example.com/testball.rb")
allow(formula).to receive_messages(core_formula?: false, missing_library_linkage: [[], Set.new])
expect { info.send(:info_formula, formula) }
expect { info.info_formula(formula) }
.to output(/==> .*testball.*\(deprecated\):/).to_stdout
.and not_to_output.to_stderr
end
@@ -356,7 +356,7 @@ RSpec.describe Homebrew::Cmd::Info do
allow(info).to receive(:github_info).with(formula).and_return("https://example.com/testball.rb")
allow(formula).to receive_messages(core_formula?: false, missing_library_linkage: [[], Set.new])
expect { info.send(:info_formula, formula) }
expect { info.info_formula(formula) }
.to output(/==> .*testball.*\(disabled\):/).to_stdout
.and not_to_output.to_stderr
end
@@ -379,7 +379,7 @@ RSpec.describe Homebrew::Cmd::Info do
to_formulae_and_casks_and_unavailable: [core, core],
)
expect { info.send(:print_info) }
expect { info.print_info }
.to output(%r{ataraxy-labs/tap/testball.*homebrew/core/testball.*Not installed}m).to_stdout
end
@@ -391,7 +391,7 @@ RSpec.describe Homebrew::Cmd::Info do
to_formulae_and_casks_and_unavailable: [error],
)
expect { info.send(:print_info) }
expect { info.print_info }
.to output(/No available formula or cask with the name "nonexistent-formula"/).to_stderr
end
@@ -409,7 +409,7 @@ RSpec.describe Homebrew::Cmd::Info do
allow(shadowing).to receive_messages(tap: Tap.fetch("ataraxy-labs/tap"), full_name: "ataraxy-labs/tap/testball")
allow(Formulary).to receive(:factory).with("ataraxy-labs/tap/testball").and_return(shadowing)
expect { info.send(:info_formula, formula) }
expect { info.info_formula(formula) }
.to output(%r{homebrew/core/testball.*Not installed.*ataraxy-labs/tap/testball}m).to_stdout
end
@@ -431,7 +431,7 @@ RSpec.describe Homebrew::Cmd::Info do
end
allow(Formulary).to receive(:factory).with("ataraxy-labs/tap/testball").and_return(keg_formula)
expect(info.send(:installed_resolution, formula)).to eq([keg_formula, shadowing_tap])
expect(info.installed_resolution(formula)).to eq([keg_formula, shadowing_tap])
end
it "resolves the keg's own name when it differs from the formula (installed via alias)" do
@@ -443,7 +443,7 @@ RSpec.describe Homebrew::Cmd::Info do
keg_formula = formula("stripe") { url "https://brew.sh/stripe-1.0.tar.gz" }
allow(Formulary).to receive(:factory).with("stripe/stripe-cli/stripe").and_return(keg_formula)
expect(info.send(:installed_resolution, formula)).to eq([keg_formula, Tap.fetch("homebrew/core")])
expect(info.installed_resolution(formula)).to eq([keg_formula, Tap.fetch("homebrew/core")])
end
it "returns the original formula and no shadowing tap when the install receipt has no tap" do
@@ -455,7 +455,7 @@ RSpec.describe Homebrew::Cmd::Info do
tab.tabfile = keg_path/AbstractTab::FILENAME
tab.write
expect(info.send(:installed_resolution, formula)).to eq([formula, nil])
expect(info.installed_resolution(formula)).to eq([formula, nil])
end
it "returns the original formula and no shadowing tap when the install receipt's tap matches" do
@@ -469,7 +469,7 @@ RSpec.describe Homebrew::Cmd::Info do
tab.write
allow(formula).to receive(:tap).and_return(Tap.fetch("homebrew/core"))
expect(info.send(:installed_resolution, formula)).to eq([formula, nil])
expect(info.installed_resolution(formula)).to eq([formula, nil])
end
it "warns about a shadowing tap when info_formula is given one" do
@@ -481,7 +481,7 @@ RSpec.describe Homebrew::Cmd::Info do
allow(info).to receive(:github_info).with(formula).and_return("https://example.com/testball.rb")
allow(formula).to receive_messages(core_formula?: false, missing_library_linkage: [[], Set.new])
expect { info.send(:info_formula, formula, shadowed_by: Tap.fetch("homebrew/core")) }
expect { info.info_formula(formula, shadowed_by: Tap.fetch("homebrew/core")) }
.to output(%r{Warning: `testball` shadows `homebrew/core/testball`}).to_stdout
.and not_to_output.to_stderr
end
@@ -495,7 +495,7 @@ RSpec.describe Homebrew::Cmd::Info do
allow(formula).to receive(:tap).and_return(Tap.fetch("homebrew/core"))
qualified = Set["homebrew/core/testball"]
expect(info.send(:formula_qualified_by_user?, formula, qualified)).to be(true)
expect(info.formula_qualified_by_user?(formula, qualified)).to be(true)
end
it "treats a bare unqualified input as not user-qualified" do
@@ -505,7 +505,7 @@ RSpec.describe Homebrew::Cmd::Info do
url "https://brew.sh/testball-0.1.tar.gz"
end
expect(info.send(:formula_qualified_by_user?, formula, Set.new)).to be(false)
expect(info.formula_qualified_by_user?(formula, Set.new)).to be(false)
end
it "--json swaps an unqualified-input formula to its installed tap" do
@@ -606,7 +606,7 @@ RSpec.describe Homebrew::Cmd::Info do
"==> Dependencies\nRequired \\(1\\): .*bar.*\n" \
"Recursive Runtime \\(2\\): 1 installed .*✔, 1 missing .*✘\nDependents: 1",
)
expect { info.send(:info_formula, formula) }
expect { info.info_formula(formula) }
.to output(expected_output).to_stdout
.and not_to_output(/^Dependencies: /).to_stdout
.and not_to_output.to_stderr
@@ -643,7 +643,7 @@ RSpec.describe Homebrew::Cmd::Info do
allow(info).to receive(:github_info).with(formula).and_return("https://example.com/testball.rb")
allow(formula).to receive_messages(core_formula?: false, missing_library_linkage: [[], Set.new])
expect { info.send(:info_formula, formula) }
expect { info.info_formula(formula) }
.to output(/^Dependents \(2\): another-dependent, some-dependent$/).to_stdout
.and not_to_output(/^Dependents: /).to_stdout
.and not_to_output.to_stderr
@@ -680,7 +680,7 @@ RSpec.describe Homebrew::Cmd::Info do
allow(formula).to receive_messages(core_formula?: false, missing_library_linkage: [[], Set.new])
allow(direct_dependency).to receive(:satisfied?).and_return(true)
expect { info.send(:info_formula, formula) }
expect { info.info_formula(formula) }
.to output(/Recursive Runtime \(1\): all installed .*✔/).to_stdout
.and not_to_output(/missing/).to_stdout
.and not_to_output.to_stderr
@@ -708,7 +708,7 @@ RSpec.describe Homebrew::Cmd::Info do
allow(info).to receive(:github_info).with(formula).and_return("https://example.com/testball.rb")
allow(formula).to receive_messages(core_formula?: false, missing_library_linkage: [[], Set.new])
expect { info.send(:info_formula, formula) }
expect { info.info_formula(formula) }
.to output(/Required \(1\): .*bar.*✘/).to_stdout
.and not_to_output.to_stderr
end
@@ -745,7 +745,7 @@ RSpec.describe Homebrew::Cmd::Info do
allow(info).to receive(:github_info).with(formula).and_return("https://example.com/testball.rb")
allow(formula).to receive_messages(core_formula?: false, missing_library_linkage: [[], Set.new])
expect { info.send(:info_formula, formula) }
expect { info.info_formula(formula) }
.to output(/Required \(1\): .*bar.*✔/).to_stdout
.and not_to_output.to_stderr
end
@@ -782,7 +782,7 @@ RSpec.describe Homebrew::Cmd::Info do
allow(info).to receive(:github_info).with(formula).and_return("https://example.com/testball.rb")
allow(formula).to receive_messages(core_formula?: false, missing_library_linkage: [[], Set.new])
expect { info.send(:info_formula, formula) }
expect { info.info_formula(formula) }
.to output(/Required \(1\): .*bar.*↑/).to_stdout
.and not_to_output.to_stderr
end
@@ -812,7 +812,7 @@ RSpec.describe Homebrew::Cmd::Info do
allow(info).to receive(:github_info).with(formula).and_return("https://example.com/testball.rb")
allow(formula).to receive_messages(core_formula?: false, missing_library_linkage: [[], Set.new])
expect { info.send(:info_formula, formula) }
expect { info.info_formula(formula) }
.to output(/Required \(1\): .*bar.*✔/).to_stdout
.and not_to_output.to_stderr
end
@@ -842,7 +842,7 @@ RSpec.describe Homebrew::Cmd::Info do
allow(info).to receive(:github_info).with(formula).and_return("https://example.com/testball.rb")
allow(formula).to receive_messages(core_formula?: false, missing_library_linkage: [[], Set.new])
expect { info.send(:info_formula, formula) }
expect { info.info_formula(formula) }
.to output(/Required \(1\): .*bar.*↑/).to_stdout
.and not_to_output.to_stderr
end
@@ -872,7 +872,7 @@ RSpec.describe Homebrew::Cmd::Info do
allow(info).to receive(:github_info).with(formula).and_return("https://example.com/testball.rb")
allow(formula).to receive_messages(core_formula?: false, missing_library_linkage: [[], Set.new])
expect { info.send(:info_formula, formula) }
expect { info.info_formula(formula) }
.to output(/Required \(1\): .*pkg-config.*✔/).to_stdout
.and not_to_output.to_stderr
end
@@ -892,7 +892,7 @@ RSpec.describe Homebrew::Cmd::Info do
allow(info).to receive(:github_info).with(formula).and_return("https://example.com/testball.rb")
allow(formula).to receive_messages(core_formula?: false, missing_library_linkage: [[], Set.new])
expect { info.send(:info_formula, formula) }
expect { info.info_formula(formula) }
.to output(/Required \(1\): bar\n/).to_stdout
.and not_to_output.to_stderr
end
@@ -919,7 +919,7 @@ RSpec.describe Homebrew::Cmd::Info do
allow(info).to receive(:github_info).with(formula).and_return("https://example.com/testball.rb")
allow(formula).to receive_messages(core_formula?: false, missing_library_linkage: [[], Set.new])
expect { info.send(:info_formula, formula) }
expect { info.info_formula(formula) }
.to output(/Required \(1\): .*bar.*✘/).to_stdout
.and not_to_output.to_stderr
end
@@ -952,7 +952,7 @@ RSpec.describe Homebrew::Cmd::Info do
allow(info).to receive(:github_info).with(formula).and_return("https://example.com/testball.rb")
allow(formula).to receive_messages(core_formula?: false, missing_library_linkage: [[], Set.new])
expect { info.send(:info_formula, formula) }
expect { info.info_formula(formula) }
.to output(/Required \(1\): .*bar.*↑/).to_stdout
.and not_to_output.to_stderr
end
@@ -981,7 +981,7 @@ RSpec.describe Homebrew::Cmd::Info do
pour_bottle?: true,
)
expect { info.send(:info_formula, formula) }
expect { info.info_formula(formula) }
.to not_to_output(/Build \(1\): .*bar.*/).to_stdout
.and not_to_output(/==> Dependencies/).to_stdout
.and not_to_output.to_stderr
@@ -1004,7 +1004,7 @@ RSpec.describe Homebrew::Cmd::Info do
tab.tabfile = keg_path/AbstractTab::FILENAME
tab.write
expect { info.send(:info_formula, formula) }
expect { info.info_formula(formula) }
.to output(/\A==> testball: 0\.0\.1 → stable 0\.1\n/).to_stdout
.and not_to_output.to_stderr
end
@@ -1025,7 +1025,7 @@ RSpec.describe Homebrew::Cmd::Info do
requirements: Requirements.new(LinuxRequirement.new),
)
expect { info.send(:info_formula, formula) }
expect { info.info_formula(formula) }
.to output(/Requirements\nRequired: .*Linux/).to_stdout
.and not_to_output(/supports Linux/).to_stdout
.and not_to_output.to_stderr
@@ -1048,7 +1048,7 @@ RSpec.describe Homebrew::Cmd::Info do
requirements: Requirements.new(os_requirement),
)
expect { info.send(:info_formula, formula) }
expect { info.info_formula(formula) }
.to not_to_output(/Installs from source: yes/).to_stdout
.and not_to_output.to_stderr
end
@@ -1077,7 +1077,7 @@ RSpec.describe Homebrew::Cmd::Info do
allow(info).to receive(:github_info).with(formula).and_return("https://example.com/testball.rb")
allow(formula).to receive_messages(core_formula?: false, missing_library_linkage: [[], Set.new])
expect { info.send(:info_formula, formula) }
expect { info.info_formula(formula) }
.to output(a_string_including("==> Binaries\nanother\ndaemon\ntestball\n")).to_stdout
.and not_to_output.to_stderr
end
@@ -1101,7 +1101,7 @@ RSpec.describe Homebrew::Cmd::Info do
allow(formula).to receive_messages(bottle:, core_formula?: false)
allow(info).to receive(:github_info).with(formula).and_return("https://example.com/testball.rb")
expect { info.send(:info_formula, formula) }
expect { info.info_formula(formula) }
.to output(a_string_including("==> Binaries\nanother\ndaemon\ntestball\n")).to_stdout
.and not_to_output.to_stderr
end
@@ -1127,7 +1127,7 @@ RSpec.describe Homebrew::Cmd::Info do
allow(info).to receive(:github_info).with(formula).and_return("https://example.com/testball.rb")
allow(formula).to receive_messages(core_formula?: false, missing_library_linkage: [[], Set.new])
expect { info.send(:info_formula, formula) }
expect { info.info_formula(formula) }
.to not_to_output(/==> Binaries/).to_stdout
.and not_to_output.to_stderr
end
@@ -1150,7 +1150,7 @@ RSpec.describe Homebrew::Cmd::Info do
allow(info).to receive(:github_info).with(formula).and_return("https://example.com/testball.rb")
allow(formula).to receive_messages(core_formula?: false, missing_library_linkage: [[], Set.new])
expect { info.send(:info_formula, formula) }
expect { info.info_formula(formula) }
.to not_to_output(/==> Binaries/).to_stdout
.and not_to_output.to_stderr
end
@@ -1239,7 +1239,7 @@ RSpec.describe Homebrew::Cmd::Info do
allow(main_formula).to receive_messages(aliases: ["testball@1.0", "tball", "googleball"], oldnames: [])
allow(info).to receive(:github_info).with(main_formula).and_return("https://example.com/testball.rb")
expect { info.send(:info_formula, main_formula) }
expect { info.info_formula(main_formula) }
.to output(/^Aliases: testball@1\.0, tball, googleball$/).to_stdout
.and not_to_output(/^Old Names:/).to_stdout
.and not_to_output.to_stderr
@@ -1254,7 +1254,7 @@ RSpec.describe Homebrew::Cmd::Info do
allow(main_formula).to receive_messages(aliases: ["testball@1.0", "tball"], oldnames: ["foo", "bar"])
allow(info).to receive(:github_info).with(main_formula).and_return("https://example.com/testball.rb")
expect { info.send(:info_formula, main_formula) }
expect { info.info_formula(main_formula) }
.to output(/^Aliases: testball@1\.0, tball\nOld Names: foo, bar$/).to_stdout
.and not_to_output.to_stderr
end
@@ -1268,7 +1268,7 @@ RSpec.describe Homebrew::Cmd::Info do
allow(main_formula).to receive_messages(aliases: [], oldnames: ["foo"])
allow(info).to receive(:github_info).with(main_formula).and_return("https://example.com/testball.rb")
expect { info.send(:info_formula, main_formula) }
expect { info.info_formula(main_formula) }
.to output(/^Old Names: foo$/).to_stdout
.and not_to_output(/^Aliases:/).to_stdout
.and not_to_output.to_stderr
@@ -1283,7 +1283,7 @@ RSpec.describe Homebrew::Cmd::Info do
allow(main_formula).to receive_messages(aliases: [], oldnames: [])
allow(info).to receive(:github_info).with(main_formula).and_return("https://example.com/testball.rb")
expect { info.send(:info_formula, main_formula) }
expect { info.info_formula(main_formula) }
.to not_to_output(/^Aliases:/).to_stdout
.and not_to_output(/^Old Names:/).to_stdout
.and not_to_output.to_stderr
@@ -1318,7 +1318,7 @@ RSpec.describe Homebrew::Cmd::Info do
allow(main_formula).to receive(:versioned_formulae).and_return([versioned])
allow(info).to receive(:github_info).with(main_formula).and_return("https://example.com/testball.rb")
expect { info.send(:info_formula, main_formula) }
expect { info.info_formula(main_formula) }
.to output(Regexp.new(
"==> Installed Versions\n" \
".*testball\\b.*\\s+1\\.0\\s+\\(.*\\)\n" \
@@ -1346,7 +1346,7 @@ RSpec.describe Homebrew::Cmd::Info do
allow(main_formula).to receive_messages(versioned_formulae: [], outdated?: true)
allow(info).to receive(:github_info).with(main_formula).and_return("https://example.com/testball.rb")
expect { info.send(:info_formula, main_formula) }
expect { info.info_formula(main_formula) }
.to output(/==> Installed Versions\n.*testball\b.*\s+1\.0 → 2\.0\s+\(/).to_stdout
.and not_to_output(/0\.9 →/).to_stdout
.and not_to_output.to_stderr
@@ -1370,7 +1370,7 @@ RSpec.describe Homebrew::Cmd::Info do
allow(main_formula).to receive(:versioned_formulae).and_return([])
allow(info).to receive(:github_info).with(main_formula).and_return("https://example.com/testball.rb")
expect { info.send(:info_formula, main_formula) }
expect { info.info_formula(main_formula) }
.to output(/==> Installed Versions\n.*testball\b.*\s+1\.0\s+\(/).to_stdout
.and not_to_output(/\s+0\.9\s+\(/).to_stdout
.and not_to_output.to_stderr
@@ -1406,7 +1406,7 @@ RSpec.describe Homebrew::Cmd::Info do
linked_version: PkgVersion.parse("1.0"))
allow(info).to receive(:github_info).with(main_formula).and_return("https://example.com/testball.rb")
expect { info.send(:info_formula, main_formula) }
expect { info.info_formula(main_formula) }
.to output(/.*testball\b.*\s+1\.0\s+\(.*\)\s+\[Linked\]/).to_stdout
.and not_to_output.to_stderr
end
@@ -1439,7 +1439,7 @@ RSpec.describe Homebrew::Cmd::Info do
allow(Formulary).to receive(:factory).with("testball").and_return(parent)
allow(info).to receive(:github_info).with(versioned).and_return("https://example.com/testball.rb")
expect { info.send(:info_formula, versioned) }
expect { info.info_formula(versioned) }
.to output(Regexp.new(
"==> Installed Versions\n" \
".*testball\\b.*\\s+1\\.0\\s+\\(.*\\)\n" \
@@ -1464,7 +1464,7 @@ RSpec.describe Homebrew::Cmd::Info do
allow(main_formula).to receive(:versioned_formulae).and_return([])
allow(info).to receive(:github_info).with(main_formula).and_return("https://example.com/testball.rb")
expect { info.send(:info_formula, main_formula) }
expect { info.info_formula(main_formula) }
.to output(/==> Installed Versions\n.*testball\b.*\s+1\.0\s+\(/).to_stdout
.and not_to_output.to_stderr
end
@@ -1491,7 +1491,7 @@ RSpec.describe Homebrew::Cmd::Info do
allow(Formulary).to receive(:factory).with("testball").and_return(parent)
allow(info).to receive(:github_info).with(versioned).and_return("https://example.com/testball.rb")
expect { info.send(:info_formula, versioned) }
expect { info.info_formula(versioned) }
.to output(/==> Installed Versions\n.*testball\b.*\s+1\.0\s+\(/).to_stdout
.and not_to_output(/testball@0\.9 \(0\.9\)/).to_stdout
.and not_to_output.to_stderr
@@ -1515,7 +1515,7 @@ RSpec.describe Homebrew::Cmd::Info do
allow(main_formula).to receive(:versioned_formulae).and_return([])
allow(info).to receive(:github_info).with(main_formula).and_return("https://example.com/testball.rb")
expect { info.send(:info_formula, main_formula) }
expect { info.info_formula(main_formula) }
.to output(Regexp.new(
"==> Installed Kegs and Versions\n" \
".*testball\\b.*\\s+1\\.0\\b.*\\(.*\\)\n" \
@@ -1536,7 +1536,7 @@ RSpec.describe Homebrew::Cmd::Info do
allow(main_formula).to receive(:versioned_formulae).and_return([])
allow(info).to receive(:github_info).with(main_formula).and_return("https://example.com/testball.rb")
expect { info.send(:info_formula, main_formula) }
expect { info.info_formula(main_formula) }
.to not_to_output(/==> Installed Versions\b/).to_stdout
.and not_to_output.to_stderr
end
@@ -1554,7 +1554,7 @@ RSpec.describe Homebrew::Cmd::Info do
url "https://brew.sh/testball-0.1.tar.gz"
end
expect(described_class.new([]).send(:github_info, formula_instance))
expect(described_class.new([]).github_info(formula_instance))
.to eq(keg_formula_path.to_s)
end
@@ -1565,7 +1565,7 @@ RSpec.describe Homebrew::Cmd::Info do
url "https://brew.sh/testball-0.1.tar.gz"
end
expect(described_class.new([]).send(:github_info, formula_instance))
expect(described_class.new([]).github_info(formula_instance))
.to eq("https://github.com/Homebrew/homebrew-core/blob/HEAD/" \
"#{formula_path.relative_path_from(tap.path)}")
end
+2 -2
View File
@@ -48,7 +48,7 @@ RSpec.describe Homebrew::Cmd::Outdated do
expect(cask).to receive(:outdated?)
.with(greedy: false, greedy_latest: false, greedy_auto_updates: false)
.and_return(false)
expect(cmd.send(:select_outdated, [cask])).to be_empty
expect(cmd.select_outdated([cask])).to be_empty
end
it "checks auto-updating casks with --greedy-auto-updates", :cask do
@@ -58,7 +58,7 @@ RSpec.describe Homebrew::Cmd::Outdated do
expect(cask).to receive(:outdated?)
.with(greedy: false, greedy_latest: false, greedy_auto_updates: true)
.and_return(true)
expect(cmd.send(:select_outdated, [cask])).to eq([cask])
expect(cmd.select_outdated([cask])).to eq([cask])
end
it "excludes auto-updating casks when auto-update upgrades are disabled", :cask do
+5 -5
View File
@@ -29,7 +29,7 @@ RSpec.describe Homebrew::Cmd::SearchCmd do
before { allow_any_instance_of(StringIO).to receive(:tty?).and_return(false) }
it "skips" do
expect { search_cmd.send(:print_missing_formula_help, "formula", false) }
expect { search_cmd.print_missing_formula_help("formula", false) }
.not_to output.to_stdout
end
end
@@ -38,25 +38,25 @@ RSpec.describe Homebrew::Cmd::SearchCmd do
before { allow_any_instance_of(StringIO).to receive(:tty?).and_return(true) }
it "skips a regex query" do
expect { search_cmd.send(:print_missing_formula_help, "/formula/", false) }
expect { search_cmd.print_missing_formula_help("/formula/", false) }
.not_to output.to_stdout
end
it "skips if there is not a reason" do
allow(Homebrew::MissingFormula).to receive(:reason).and_return(nil)
expect { search_cmd.send(:print_missing_formula_help, "formula", false) }
expect { search_cmd.print_missing_formula_help("formula", false) }
.not_to output.to_stdout
end
it "prints additional output if `found_matches` is true" do
allow(Homebrew::MissingFormula).to receive(:reason).and_return("Reason")
expect { search_cmd.send(:print_missing_formula_help, "formula", true) }
expect { search_cmd.print_missing_formula_help("formula", true) }
.to output("\nIf you meant \"formula\" specifically:\nReason\n").to_stdout
end
it "only prints reason if `found_matches` is false" do
allow(Homebrew::MissingFormula).to receive(:reason).and_return("Reason")
expect { search_cmd.send(:print_missing_formula_help, "formula", false) }
expect { search_cmd.print_missing_formula_help("formula", false) }
.to output("Reason\n").to_stdout
end
end
@@ -17,7 +17,7 @@ RSpec.shared_examples "parseable arguments" do |command_name: nil|
klass = Object.const_get(command)
# rubocop:enable Sorbet/ConstantsFromStrings
end
argv = klass.parser.instance_variable_get(:@min_named_args)&.times&.map { "argument" } || []
argv = klass.parser.min_named_args&.times&.map { "argument" } || []
cmd = klass.new(argv)
expect(cmd.args).to be_a Homebrew::CLI::Args
end
+28 -26
View File
@@ -16,85 +16,85 @@ RSpec.describe Homebrew::Cmd::Source do
describe "#github_repo_url" do
it "extracts repository URL from GitHub URL" do
expect(described_class.new([]).send(:github_repo_url, "https://github.com/Homebrew/brew.git"))
expect(described_class.new([]).github_repo_url("https://github.com/Homebrew/brew.git"))
.to eq("https://github.com/Homebrew/brew")
end
it "handles GitHub archive URLs" do
expect(described_class.new([]).send(:github_repo_url, "https://github.com/Homebrew/testball/archive/refs/tags/v0.1.tar.gz"))
expect(described_class.new([]).github_repo_url("https://github.com/Homebrew/testball/archive/refs/tags/v0.1.tar.gz"))
.to eq("https://github.com/Homebrew/testball")
end
it "returns nil for non-GitHub URLs" do
expect(described_class.new([]).send(:github_repo_url, "https://example.com/repo.git"))
expect(described_class.new([]).github_repo_url("https://example.com/repo.git"))
.to be_nil
end
end
describe "#gitlab_repo_url" do
it "extracts repository URL from GitLab URL with nested groups" do
expect(described_class.new([]).send(:gitlab_repo_url, "https://gitlab.com/group/subgroup/project/-/archive/v1.0/project-v1.0.tar.gz"))
expect(described_class.new([]).gitlab_repo_url("https://gitlab.com/group/subgroup/project/-/archive/v1.0/project-v1.0.tar.gz"))
.to eq("https://gitlab.com/group/subgroup/project")
end
it "handles GitLab .git URLs" do
expect(described_class.new([]).send(:gitlab_repo_url, "https://gitlab.com/user/repo.git"))
expect(described_class.new([]).gitlab_repo_url("https://gitlab.com/user/repo.git"))
.to eq("https://gitlab.com/user/repo")
end
it "returns nil for non-GitLab URLs" do
expect(described_class.new([]).send(:gitlab_repo_url, "https://example.com/repo.git"))
expect(described_class.new([]).gitlab_repo_url("https://example.com/repo.git"))
.to be_nil
end
end
describe "#bitbucket_repo_url" do
it "extracts repository URL from Bitbucket URL" do
expect(described_class.new([]).send(:bitbucket_repo_url, "https://bitbucket.org/user/repo/get/v1.0.tar.gz"))
expect(described_class.new([]).bitbucket_repo_url("https://bitbucket.org/user/repo/get/v1.0.tar.gz"))
.to eq("https://bitbucket.org/user/repo")
end
it "handles Bitbucket .git URLs" do
expect(described_class.new([]).send(:bitbucket_repo_url, "https://bitbucket.org/user/repo.git"))
expect(described_class.new([]).bitbucket_repo_url("https://bitbucket.org/user/repo.git"))
.to eq("https://bitbucket.org/user/repo")
end
it "returns nil for non-Bitbucket URLs" do
expect(described_class.new([]).send(:bitbucket_repo_url, "https://example.com/repo.git"))
expect(described_class.new([]).bitbucket_repo_url("https://example.com/repo.git"))
.to be_nil
end
end
describe "#codeberg_repo_url" do
it "extracts repository URL from Codeberg URL" do
expect(described_class.new([]).send(:codeberg_repo_url, "https://codeberg.org/user/repo/archive/v1.0.tar.gz"))
expect(described_class.new([]).codeberg_repo_url("https://codeberg.org/user/repo/archive/v1.0.tar.gz"))
.to eq("https://codeberg.org/user/repo")
end
it "handles Codeberg .git URLs" do
expect(described_class.new([]).send(:codeberg_repo_url, "https://codeberg.org/user/repo.git"))
expect(described_class.new([]).codeberg_repo_url("https://codeberg.org/user/repo.git"))
.to eq("https://codeberg.org/user/repo")
end
it "returns nil for non-Codeberg URLs" do
expect(described_class.new([]).send(:codeberg_repo_url, "https://example.com/repo.git"))
expect(described_class.new([]).codeberg_repo_url("https://example.com/repo.git"))
.to be_nil
end
end
describe "#sourcehut_repo_url" do
it "extracts repository URL from SourceHut URL" do
expect(described_class.new([]).send(:sourcehut_repo_url, "https://git.sr.ht/~user/repo/archive/v1.0.tar.gz"))
expect(described_class.new([]).sourcehut_repo_url("https://git.sr.ht/~user/repo/archive/v1.0.tar.gz"))
.to eq("https://sr.ht/~user/repo")
end
it "handles sr.ht URLs without git subdomain" do
expect(described_class.new([]).send(:sourcehut_repo_url, "https://sr.ht/~user/repo"))
expect(described_class.new([]).sourcehut_repo_url("https://sr.ht/~user/repo"))
.to eq("https://sr.ht/~user/repo")
end
it "returns nil for non-SourceHut URLs" do
expect(described_class.new([]).send(:sourcehut_repo_url, "https://example.com/repo.git"))
expect(described_class.new([]).sourcehut_repo_url("https://example.com/repo.git"))
.to be_nil
end
end
@@ -118,8 +118,9 @@ RSpec.describe Homebrew::Cmd::Source do
])
expect(described_class.new([])
.send(:pypi_repo_url,
"https://files.pythonhosted.org/packages/24/62/ae72ff66c0f1fd959925b4c11f8c2dea61f47f6acaea75a08512cdfe3fed/numpy-2.4.1.tar.gz"))
.pypi_repo_url(
"https://files.pythonhosted.org/packages/24/62/ae72ff66c0f1fd959925b4c11f8c2dea61f47f6acaea75a08512cdfe3fed/numpy-2.4.1.tar.gz",
))
.to eq("https://github.com/numpy/numpy")
end
@@ -139,13 +140,14 @@ RSpec.describe Homebrew::Cmd::Source do
])
expect(described_class.new([])
.send(:pypi_repo_url,
"https://files.pythonhosted.org/packages/00/00/000000000000000000000000000000000000000000000000000000000000/foobar-0.0.1.tar.gz"))
.pypi_repo_url(
"https://files.pythonhosted.org/packages/00/00/000000000000000000000000000000000000000000000000000000000000/foobar-0.0.1.tar.gz",
))
.to be_nil
end
it "returns nil for non-PyPI URLs" do
expect(described_class.new([]).send(:pypi_repo_url, "https://example.com/repo.git"))
expect(described_class.new([]).pypi_repo_url("https://example.com/repo.git"))
.to be_nil
end
end
@@ -168,7 +170,7 @@ RSpec.describe Homebrew::Cmd::Source do
instance_double(Process::Status, success?: true),
])
expect(described_class.new([]).send(:npm_repo_url, "https://registry.npmjs.org/#{package}/-/vite-1.2.3.tgz"))
expect(described_class.new([]).npm_repo_url("https://registry.npmjs.org/#{package}/-/vite-1.2.3.tgz"))
.to eq("https://github.com/vitejs/vite.git")
end
end
@@ -183,29 +185,29 @@ RSpec.describe Homebrew::Cmd::Source do
])
expect(described_class.new([])
.send(:npm_repo_url, "https://registry.npmjs.org/vite/-/vite-1.2.3.tgz"))
.npm_repo_url("https://registry.npmjs.org/vite/-/vite-1.2.3.tgz"))
.to be_nil
end
it "returns nil for non-npm URLs" do
expect(described_class.new([]).send(:npm_repo_url, "https://example.com/repo.git"))
expect(described_class.new([]).npm_repo_url("https://example.com/repo.git"))
.to be_nil
end
end
describe "#url_to_repo" do
it "returns GitHub repo URL for GitHub URLs" do
expect(described_class.new([]).send(:url_to_repo, "https://github.com/Homebrew/brew"))
expect(described_class.new([]).url_to_repo("https://github.com/Homebrew/brew"))
.to eq("https://github.com/Homebrew/brew")
end
it "returns GitLab repo URL for GitLab URLs" do
expect(described_class.new([]).send(:url_to_repo, "https://gitlab.com/user/repo.git"))
expect(described_class.new([]).url_to_repo("https://gitlab.com/user/repo.git"))
.to eq("https://gitlab.com/user/repo")
end
it "returns nil for unsupported URLs" do
expect(described_class.new([]).send(:url_to_repo, "https://example.com/repo.tar.gz"))
expect(described_class.new([]).url_to_repo("https://example.com/repo.tar.gz"))
.to be_nil
end
end
+20 -20
View File
@@ -48,7 +48,7 @@ RSpec.describe Homebrew::Cmd::TapInfo do
allow(Homebrew::Trust).to receive(:trusted_tap?).with(tap).and_return(true)
with_env(HOMEBREW_REQUIRE_TAP_TRUST: "1") do
expect { tap_info.send(:print_tap_info, [tap]) }
expect { tap_info.print_tap_info([tap]) }
.to output(%r{thirdparty/foo: Installed\nTrusted\nNo commands/casks/formulae}).to_stdout
end
end
@@ -76,7 +76,7 @@ RSpec.describe Homebrew::Cmd::TapInfo do
allow(Homebrew::Trust).to receive(:trusted_tap?).with(tap).and_return(false)
with_env(HOMEBREW_REQUIRE_TAP_TRUST: "1") do
expect { tap_info.send(:print_tap_info, [tap]) }
expect { tap_info.print_tap_info([tap]) }
.to output(%r{thirdparty/foo: Installed\nUntrusted\nNo commands/casks/formulae}).to_stdout
end
end
@@ -88,7 +88,7 @@ RSpec.describe Homebrew::Cmd::TapInfo do
instance_double(Tap, to_hash: { "name" => "user/b" }),
]
expect { described_class.new([]).send(:print_tap_json, taps) }
expect { described_class.new([]).print_tap_json(taps) }
.to output(%r{"name":\s*"user/a".*"name":\s*"user/b"}m).to_stdout
end
end
@@ -103,35 +103,35 @@ RSpec.describe Homebrew::Cmd::TapInfo do
end
it "does not mark an uninstalled formula" do
expect(tap_info.send(:decorate_formula, tap, "missing", installed: false)).not_to include("")
expect(tap_info.decorate_formula(tap, "missing", installed: false)).not_to include("")
end
it "marks an installed formula as satisfied" do
formula = instance_double(Formula, outdated?: false, deprecated?: false, disabled?: false)
allow(Formulary).to receive(:factory).with("homebrew/foo/installed").and_return(formula)
expect(tap_info.send(:decorate_formula, tap, "installed", installed: true)).to match(/installed.*✔/)
expect(tap_info.decorate_formula(tap, "installed", installed: true)).to match(/installed.*✔/)
end
it "marks an outdated installed formula as upgradable" do
formula = instance_double(Formula, outdated?: true, deprecated?: false, disabled?: false)
allow(Formulary).to receive(:factory).with("homebrew/foo/outdated").and_return(formula)
expect(tap_info.send(:decorate_formula, tap, "outdated", installed: true)).to match(/outdated.*↑/)
expect(tap_info.decorate_formula(tap, "outdated", installed: true)).to match(/outdated.*↑/)
end
it "marks a deprecated formula with `(deprecated)`" do
formula = instance_double(Formula, outdated?: false, deprecated?: true, disabled?: false)
allow(Formulary).to receive(:factory).with("homebrew/foo/old").and_return(formula)
expect(tap_info.send(:decorate_formula, tap, "old", installed: false)).to match(/old.*\(deprecated\)/)
expect(tap_info.decorate_formula(tap, "old", installed: false)).to match(/old.*\(deprecated\)/)
end
it "marks a disabled formula with `(disabled)`" do
formula = instance_double(Formula, outdated?: false, deprecated?: false, disabled?: true)
allow(Formulary).to receive(:factory).with("homebrew/foo/gone").and_return(formula)
expect(tap_info.send(:decorate_formula, tap, "gone", installed: false)).to match(/gone.*\(disabled\)/)
expect(tap_info.decorate_formula(tap, "gone", installed: false)).to match(/gone.*\(disabled\)/)
end
end
@@ -145,35 +145,35 @@ RSpec.describe Homebrew::Cmd::TapInfo do
end
it "does not mark an uninstalled cask" do
expect(tap_info.send(:decorate_cask, tap, "missing", installed: false)).not_to include("")
expect(tap_info.decorate_cask(tap, "missing", installed: false)).not_to include("")
end
it "marks an installed cask as satisfied" do
cask = instance_double(Cask::Cask, outdated?: false, deprecated?: false, disabled?: false)
allow(Cask::CaskLoader).to receive(:load).with("homebrew/foo/installed").and_return(cask)
expect(tap_info.send(:decorate_cask, tap, "installed", installed: true)).to match(/installed.*✔/)
expect(tap_info.decorate_cask(tap, "installed", installed: true)).to match(/installed.*✔/)
end
it "marks an outdated installed cask as upgradable" do
cask = instance_double(Cask::Cask, outdated?: true, deprecated?: false, disabled?: false)
allow(Cask::CaskLoader).to receive(:load).with("homebrew/foo/outdated").and_return(cask)
expect(tap_info.send(:decorate_cask, tap, "outdated", installed: true)).to match(/outdated.*↑/)
expect(tap_info.decorate_cask(tap, "outdated", installed: true)).to match(/outdated.*↑/)
end
it "marks a deprecated cask with `(deprecated)`" do
cask = instance_double(Cask::Cask, outdated?: false, deprecated?: true, disabled?: false)
allow(Cask::CaskLoader).to receive(:load).with("homebrew/foo/old").and_return(cask)
expect(tap_info.send(:decorate_cask, tap, "old", installed: false)).to match(/old.*\(deprecated\)/)
expect(tap_info.decorate_cask(tap, "old", installed: false)).to match(/old.*\(deprecated\)/)
end
it "marks a disabled cask with `(disabled)`" do
cask = instance_double(Cask::Cask, outdated?: false, deprecated?: false, disabled?: true)
allow(Cask::CaskLoader).to receive(:load).with("homebrew/foo/gone").and_return(cask)
expect(tap_info.send(:decorate_cask, tap, "gone", installed: false)).to match(/gone.*\(disabled\)/)
expect(tap_info.decorate_cask(tap, "gone", installed: false)).to match(/gone.*\(disabled\)/)
end
end
@@ -218,11 +218,11 @@ RSpec.describe Homebrew::Cmd::TapInfo do
end
it "lists every formula and cask, marking only the installed ones" do
expect { tap_info.send(:print_tap_listings, tap) }
expect { tap_info.print_tap_listings(tap) }
.to output(
/Commands.*mycmd.*==> Formulae.*foo.*✔.*uninstalled-formula.*==> Casks.*bar.*✔.*uninstalled-cask/m,
).to_stdout
expect { tap_info.send(:print_tap_listings, tap) }.not_to output(//).to_stdout
expect { tap_info.print_tap_listings(tap) }.not_to output(//).to_stdout
end
end
@@ -249,7 +249,7 @@ RSpec.describe Homebrew::Cmd::TapInfo do
end
it "warns about truncation and shows only installed entries under the standard header" do
expect { tap_info.send(:print_tap_listings, tap) }
expect { tap_info.print_tap_listings(tap) }
.to output(/==> Formulae.*formula7.*✔/m).to_stdout
.and output(/Tap has more than 30 formulae; showing only installed entries\./).to_stderr
end
@@ -273,9 +273,9 @@ RSpec.describe Homebrew::Cmd::TapInfo do
end
it "lists every formula and cask without uninstalled markers" do
expect { tap_info.send(:print_tap_listings, tap) }
expect { tap_info.print_tap_listings(tap) }
.to output(/==> Formulae.*baz.*foo.*==> Casks.*bar/m).to_stdout
expect { tap_info.send(:print_tap_listings, tap) }.not_to output(//).to_stdout
expect { tap_info.print_tap_listings(tap) }.not_to output(//).to_stdout
end
end
@@ -298,13 +298,13 @@ RSpec.describe Homebrew::Cmd::TapInfo do
end
it "shows a link to the tap remote and warns when nothing is installed" do
expect { tap_info.send(:print_tap_listings, tap) }
expect { tap_info.print_tap_listings(tap) }
.to output(%r{See: https://github.com/homebrew/homebrew-foo}).to_stdout
.and output(/Tap has more than 30 formulae and none are installed\./).to_stderr
end
it "does not list individual formula names" do
expect { tap_info.send(:print_tap_listings, tap) }
expect { tap_info.print_tap_listings(tap) }
.not_to output(/formula1\b/).to_stdout
end
end
+13 -12
View File
@@ -15,7 +15,7 @@ RSpec.describe Homebrew::Cmd::UpdateReport do
it "links to the donations section" do
allow(Homebrew::Settings).to receive(:read).with("donationmessage").and_return("false")
expect { described_class.new([]).send(:donation_message) }
expect { described_class.new([]).donation_message }
.to output(include("https://github.com/Homebrew/brew#-donations")).to_stdout
end
@@ -471,7 +471,7 @@ RSpec.describe Homebrew::Cmd::UpdateReport do
expect(hub.select_formula_or_cask(:A)).to be_empty
expect(hub.select_formula_or_cask(:D)).to be_empty
expect(hub.instance_variable_get(:@hash)[:R]).to eq([["cv", "progress"]])
expect(hub.renamed_formulae).to eq([["cv", "progress"]])
end
context "when updating a Tap other than the core Tap" do
@@ -490,7 +490,7 @@ RSpec.describe Homebrew::Cmd::UpdateReport do
expect(hub.select_formula_or_cask(:A)).to be_empty
expect(hub.select_formula_or_cask(:D)).to be_empty
expect(hub.instance_variable_get(:@hash)[:R]).to be_nil
expect(hub.renamed_formulae).to be_empty
end
specify "with renamed Formula and restructured Tap" do
@@ -499,7 +499,7 @@ RSpec.describe Homebrew::Cmd::UpdateReport do
expect(hub.select_formula_or_cask(:A)).to be_empty
expect(hub.select_formula_or_cask(:D)).to be_empty
expect(hub.instance_variable_get(:@hash)[:R]).to eq([%w[foo/bar/xchat foo/bar/xchat2]])
expect(hub.renamed_formulae).to eq([%w[foo/bar/xchat foo/bar/xchat2]])
end
specify "with simulated 'homebrew/php' restructuring" do
@@ -507,7 +507,7 @@ RSpec.describe Homebrew::Cmd::UpdateReport do
expect(hub.select_formula_or_cask(:A)).to be_empty
expect(hub.select_formula_or_cask(:D)).to be_empty
expect(hub.instance_variable_get(:@hash)[:R]).to be_nil
expect(hub.renamed_formulae).to be_empty
end
specify "with Formula changes" do
@@ -515,7 +515,7 @@ RSpec.describe Homebrew::Cmd::UpdateReport do
expect(hub.select_formula_or_cask(:A)).to eq(%w[foo/bar/lua])
expect(hub.select_formula_or_cask(:M)).to eq(%w[foo/bar/git])
expect(hub.instance_variable_get(:@hash)[:R]).to be_nil
expect(hub.renamed_formulae).to be_empty
end
specify "with formula migrated to cask in same tap" do
@@ -545,19 +545,19 @@ RSpec.describe Homebrew::Cmd::UpdateReport do
it "recommends trusting just the migrated package then migrating a rename" do
expect(other_tap).not_to receive(:ensure_installed!)
expect { reporter.send(:ensure_trusted_tap_installed!, "oldfoo", "newfoo", other_tap) }
expect { reporter.ensure_trusted_tap_installed!("oldfoo", "newfoo", other_tap) }
.to output(%r{brew trust foo/bar/newfoo.*brew migrate oldfoo}m).to_stderr
end
it "recommends a reinstall for an unchanged-name tap migration" do
expect { reporter.send(:ensure_trusted_tap_installed!, "foo", "foo", other_tap) }
expect { reporter.ensure_trusted_tap_installed!("foo", "foo", other_tap) }
.to output(/brew reinstall foo/).to_stderr
end
it "taps a trusted tap" do
allow(other_tap).to receive(:official?).and_return(true)
expect(other_tap).to receive(:ensure_installed!)
reporter.send(:ensure_trusted_tap_installed!, "foo", "foo", other_tap)
reporter.ensure_trusted_tap_installed!("foo", "foo", other_tap)
end
end
@@ -577,7 +577,7 @@ RSpec.describe Homebrew::Cmd::UpdateReport do
-baz
DIFF
expect(reporter.send(:diff)).to eq(<<~DIFF.strip)
expect(reporter.diff).to eq(<<~DIFF.strip)
A api/bar.rb
D api/baz.rb
DIFF
@@ -593,7 +593,7 @@ RSpec.describe Homebrew::Cmd::UpdateReport do
-baz
DIFF
expect(reporter.send(:diff)).to eq(<<~DIFF.strip)
expect(reporter.diff).to eq(<<~DIFF.strip)
A api/baz.rb
DIFF
end
@@ -698,13 +698,14 @@ RSpec.describe Homebrew::Cmd::UpdateReport do
end
it "merges frozen report arrays" do
allow(hub).to receive(:select_formula_or_cask).and_call_original
first_reporter = instance_double(Reporter, report: { A: ["foo"].freeze })
second_reporter = instance_double(Reporter, report: { A: ["bar"] })
hub.add(first_reporter)
hub.add(second_reporter)
expect(hub.instance_variable_get(:@hash)[:A]).to eq(%w[foo bar])
expect(hub.select_formula_or_cask(:A)).to eq(%w[foo bar])
end
end
end
+21 -22
View File
@@ -362,7 +362,7 @@ RSpec.describe Homebrew::Cmd::UpgradeCmd do
allow(Cask::Upgrade).to receive(:upgrade_casks!).and_raise(Cask::CaskError.new("test cask error"))
cmd = described_class.new(["--cask"])
expect { cmd.send(:upgrade_outdated_casks!, []) }
expect { cmd.upgrade_outdated_casks!([]) }
.to output(/test cask error/).to_stderr
expect(Homebrew).to have_failed
@@ -374,7 +374,7 @@ RSpec.describe Homebrew::Cmd::UpgradeCmd do
expect(Homebrew::Install).not_to receive(:ask_casks)
expect(Cask::Upgrade).to receive(:upgrade_casks!).and_return(true)
cmd.send(:upgrade_outdated_casks!, [])
cmd.upgrade_outdated_casks!([])
end
it "passes --no-quit to cask upgrades" do
@@ -385,7 +385,7 @@ RSpec.describe Homebrew::Cmd::UpgradeCmd do
true
end
cmd.send(:upgrade_outdated_casks!, [])
cmd.upgrade_outdated_casks!([])
end
it "passes HOMEBREW_NO_UPGRADE_QUIT_CASKS to cask upgrades" do
@@ -397,7 +397,7 @@ RSpec.describe Homebrew::Cmd::UpgradeCmd do
true
end
cmd.send(:upgrade_outdated_casks!, [])
cmd.upgrade_outdated_casks!([])
end
end
@@ -409,7 +409,7 @@ RSpec.describe Homebrew::Cmd::UpgradeCmd do
expect(cmd).to receive(:upgrade_outdated_formulae!)
.with([], dry_run: true, show_upgrade_summary: false)
.ordered do
cmd.send(:final_upgrade_summary).version_changes << "testball 0.1 -> 0.2"
cmd.final_upgrade_summary.version_changes << "testball 0.1 -> 0.2"
true
end
expect(cmd).to receive(:upgrade_outdated_casks!)
@@ -531,7 +531,7 @@ RSpec.describe Homebrew::Cmd::UpgradeCmd do
expect(cmd).to receive(:upgrade_outdated_formulae!)
.with([formula], dry_run: true, show_upgrade_summary: false)
.ordered do
cmd.send(:final_upgrade_summary).version_changes << "testball 0.1 -> 0.2"
cmd.final_upgrade_summary.version_changes << "testball 0.1 -> 0.2"
true
end
allow(cmd).to receive(:show_final_upgrade_summary).and_call_original
@@ -560,7 +560,7 @@ RSpec.describe Homebrew::Cmd::UpgradeCmd do
allow(formula).to receive_messages(optlinked?: true, opt_prefix: HOMEBREW_PREFIX/"opt/testball", bottle:)
allow(Keg).to receive(:new).with(HOMEBREW_PREFIX/"opt/testball").and_return(keg)
expect(cmd.send(:formula_upgrade_descriptions, [formula], include_sizes: true))
expect(cmd.formula_upgrade_descriptions([formula], include_sizes: true))
.to eq(["testball 0.1 -> 0.2 (500B)"])
end
@@ -578,7 +578,7 @@ RSpec.describe Homebrew::Cmd::UpgradeCmd do
allow(Keg).to receive(:new).with(HOMEBREW_PREFIX/"opt/testball").and_return(keg)
expect(bottle).not_to receive(:fetch_tab)
expect(cmd.send(:formula_upgrade_descriptions, [formula], include_sizes: true))
expect(cmd.formula_upgrade_descriptions([formula], include_sizes: true))
.to eq(["testball 0.1 -> 0.2"])
end
@@ -697,7 +697,7 @@ RSpec.describe Homebrew::Cmd::UpgradeCmd do
allow(cmd).to receive(:final_upgrade_summary).and_return(summary)
expect { cmd.send(:show_final_upgrade_summary) }.to output(<<~EOS).to_stdout
expect { cmd.show_final_upgrade_summary }.to output(<<~EOS).to_stdout
==> Would upgrade 2 outdated packages
testball 0.1 -> 0.2 (500B)
codex 1.0 -> 2.0
@@ -784,7 +784,7 @@ RSpec.describe Homebrew::Cmd::UpgradeCmd do
use_prefetched: false, prefetch_names: nil,
prefetch_upgrades: nil, **|
if dry_run
cmd.send(:final_upgrade_summary).version_changes << "deno 2.7.10 -> 2.7.11"
cmd.final_upgrade_summary.version_changes << "deno 2.7.10 -> 2.7.11"
elsif prefetch_only
prefetch_names&.replace(["deno"])
prefetch_upgrades&.replace(["deno 2.7.10 -> 2.7.11"])
@@ -937,8 +937,7 @@ RSpec.describe Homebrew::Cmd::UpgradeCmd do
expect(cmd).not_to receive(:ofail)
expect(
cmd.send(
:prefetch_outdated_casks!,
cmd.prefetch_outdated_casks!(
[],
download_queue:,
prefetch_names:,
@@ -1019,7 +1018,7 @@ RSpec.describe Homebrew::Cmd::UpgradeCmd do
allow(Homebrew::Upgrade).to receive(:formula_installers).and_return([])
expect do
cmd.send(:formulae_upgrade_context, [formula], show_upgrade_summary: false)
cmd.formulae_upgrade_context([formula], show_upgrade_summary: false)
end.to output("==> Downloading bottle manifests\n").to_stdout
end
@@ -1042,7 +1041,7 @@ RSpec.describe Homebrew::Cmd::UpgradeCmd do
allow(Homebrew::Upgrade).to receive(:formula_installers).and_return([])
expect do
cmd.send(:formulae_upgrade_context, [formula], show_upgrade_summary: false)
cmd.formulae_upgrade_context([formula], show_upgrade_summary: false)
end.not_to output(/Downloading bottle manifests/).to_stdout
end
@@ -1116,7 +1115,7 @@ RSpec.describe Homebrew::Cmd::UpgradeCmd do
cmd = described_class.new(["--cask", "--dry-run"])
expect { cmd.send(:upgrade_outdated_casks!, []) }
expect { cmd.upgrade_outdated_casks!([]) }
.to not_to_output(/Unexpected method 'discontinued' called during caveats on Cask local-caffeine\./).to_stderr
end
@@ -1133,7 +1132,7 @@ RSpec.describe Homebrew::Cmd::UpgradeCmd do
allow(cmd).to receive(:final_upgrade_summary).and_return(summary)
expect { cmd.send(:show_final_upgrade_summary) }.to output(<<~EOS).to_stdout
expect { cmd.show_final_upgrade_summary }.to output(<<~EOS).to_stdout
==> Upgraded 1 outdated package
testball 0.1 -> 0.2
==> 1 Pinned formula
@@ -1188,8 +1187,8 @@ RSpec.describe Homebrew::Cmd::UpgradeCmd do
pinned_formulae: [pinned],
)
cmd.send(:record_formula_upgrade_summary, context)
summary = cmd.send(:final_upgrade_summary)
cmd.record_formula_upgrade_summary(context)
summary = cmd.final_upgrade_summary
expect(summary.version_changes).to include("testball 0.1 -> 0.2")
expect(summary.pinned_formulae).to include("pinnedball 1.0")
@@ -1224,9 +1223,9 @@ RSpec.describe Homebrew::Cmd::UpgradeCmd do
end
allow(Homebrew::Upgrade).to receive(:upgrade_dependents)
cmd.send(:upgrade_outdated_formulae!, [])
cmd.upgrade_outdated_formulae!([])
expect(cmd.send(:final_upgrade_summary).version_changes).to include("testball 0.1 -> 0.2")
expect(cmd.final_upgrade_summary.version_changes).to include("testball 0.1 -> 0.2")
end
it "omits failed formula version changes from the final summary" do
@@ -1259,9 +1258,9 @@ RSpec.describe Homebrew::Cmd::UpgradeCmd do
allow(Homebrew::Upgrade).to receive(:upgrade_formulae).and_return([successful_formula_installer])
allow(Homebrew::Upgrade).to receive(:upgrade_dependents)
cmd.send(:upgrade_outdated_formulae!, [])
cmd.upgrade_outdated_formulae!([])
expect(cmd.send(:final_upgrade_summary)).to have_attributes(
expect(cmd.final_upgrade_summary).to have_attributes(
version_changes: contain_exactly("testball 0.1 -> 0.2"),
deprecated: contain_exactly("failball"),
)
@@ -174,6 +174,6 @@ RSpec.describe Dependency do
allow(foo).to receive(:to_formula).and_raise(FormulaUnavailableError, foo.name)
f = instance_double(Formula, name: "f", deps: [foo])
expect { described_class.expand(f) }.to raise_error(FormulaUnavailableError)
expect(described_class.instance_variable_get(:@expand_stack)).to be_empty
expect(described_class.expand_stack).to be_empty
end
end
@@ -85,12 +85,12 @@ RSpec.describe Homebrew::DevCmd::BumpCaskPr do
context "when cask does not have on_system blocks/calls or `depends_on arch`" do
it "returns an array only including macOS/ARM" do
Homebrew::SimulateSystem.with(os: :linux) do
expect(bump_cask_pr.send(:generate_system_options, c, new_version))
expect(bump_cask_pr.generate_system_options(c, new_version))
.to eq([[newest_macos, :arm]])
end
Homebrew::SimulateSystem.with(os: older_macos) do
expect(bump_cask_pr.send(:generate_system_options, c, new_version))
expect(bump_cask_pr.generate_system_options(c, new_version))
.to eq([[older_macos, :arm]])
end
end
@@ -99,12 +99,12 @@ RSpec.describe Homebrew::DevCmd::BumpCaskPr do
context "when cask does not have on_system blocks/calls but has `depends_on arch`" do
it "returns an array only including macOS/`depends_on arch` value" do
Homebrew::SimulateSystem.with(os: :linux, arch: :arm) do
expect(bump_cask_pr.send(:generate_system_options, c_depends_on_intel, new_version))
expect(bump_cask_pr.generate_system_options(c_depends_on_intel, new_version))
.to eq([[newest_macos, :intel]])
end
Homebrew::SimulateSystem.with(os: older_macos, arch: :arm) do
expect(bump_cask_pr.send(:generate_system_options, c_depends_on_intel, new_version))
expect(bump_cask_pr.generate_system_options(c_depends_on_intel, new_version))
.to eq([[older_macos, :intel]])
end
end
@@ -113,7 +113,7 @@ RSpec.describe Homebrew::DevCmd::BumpCaskPr do
context "when cask has on_system blocks/calls but does not have `depends_on arch`" do
it "returns an array with combinations of `OnSystem::BASE_OS_OPTIONS` and `OnSystem::ARCH_OPTIONS`" do
Homebrew::SimulateSystem.with(os: :linux) do
expect(bump_cask_pr.send(:generate_system_options, c_on_system, new_version))
expect(bump_cask_pr.generate_system_options(c_on_system, new_version))
.to eq([
[newest_macos, :intel],
[newest_macos, :arm],
@@ -123,7 +123,7 @@ RSpec.describe Homebrew::DevCmd::BumpCaskPr do
end
Homebrew::SimulateSystem.with(os: older_macos) do
expect(bump_cask_pr.send(:generate_system_options, c_on_system, new_version))
expect(bump_cask_pr.generate_system_options(c_on_system, new_version))
.to eq([
[older_macos, :intel],
[older_macos, :arm],
@@ -137,7 +137,7 @@ RSpec.describe Homebrew::DevCmd::BumpCaskPr do
context "when cask has on_system blocks/calls and `depends_on arch`" do
it "returns an array with combinations of `OnSystem::BASE_OS_OPTIONS` and `OnSystem::ARCH_OPTIONS`" do
Homebrew::SimulateSystem.with(os: :linux, arch: :arm) do
expect(bump_cask_pr.send(:generate_system_options, c_on_system_depends_on_intel, new_version))
expect(bump_cask_pr.generate_system_options(c_on_system_depends_on_intel, new_version))
.to eq([
[newest_macos, :intel],
[newest_macos, :arm],
@@ -147,7 +147,7 @@ RSpec.describe Homebrew::DevCmd::BumpCaskPr do
end
Homebrew::SimulateSystem.with(os: older_macos, arch: :arm) do
expect(bump_cask_pr.send(:generate_system_options, c_on_system_depends_on_intel, new_version))
expect(bump_cask_pr.generate_system_options(c_on_system_depends_on_intel, new_version))
.to eq([
[older_macos, :intel],
[older_macos, :arm],
@@ -177,7 +177,7 @@ RSpec.describe Homebrew::DevCmd::BumpCaskPr do
end
expect(cask.depends_on.arch).to eq([{ type: :arm, bits: 64 }])
expect(bump_cask_pr.send(:generate_system_options, cask, new_version))
expect(bump_cask_pr.generate_system_options(cask, new_version))
.to eq([
[older_macos, :intel],
[older_macos, :arm],
@@ -196,24 +196,24 @@ RSpec.describe Homebrew::DevCmd::BumpCaskPr do
it "returns an array only using archs of arch-specific versions" do
Homebrew::SimulateSystem.with(os: :linux) do
expect(bump_cask_pr.send(:generate_system_options, c_arm_intel, new_version_arm))
expect(bump_cask_pr.generate_system_options(c_arm_intel, new_version_arm))
.to eq([
[newest_macos, :arm],
[:linux, :arm],
])
expect(bump_cask_pr.send(:generate_system_options, c_arm_intel, new_version_intel))
expect(bump_cask_pr.generate_system_options(c_arm_intel, new_version_intel))
.to eq([
[newest_macos, :intel],
[:linux, :intel],
])
expect(bump_cask_pr.send(:generate_system_options, c_arm_intel, new_version_arm_intel))
expect(bump_cask_pr.generate_system_options(c_arm_intel, new_version_arm_intel))
.to eq([
[newest_macos, :arm],
[newest_macos, :intel],
[:linux, :arm],
[:linux, :intel],
])
expect(bump_cask_pr.send(:generate_system_options, c_arm_intel, new_version_intel_arm))
expect(bump_cask_pr.generate_system_options(c_arm_intel, new_version_intel_arm))
.to eq([
[newest_macos, :intel],
[newest_macos, :arm],
@@ -223,24 +223,24 @@ RSpec.describe Homebrew::DevCmd::BumpCaskPr do
end
Homebrew::SimulateSystem.with(os: older_macos) do
expect(bump_cask_pr.send(:generate_system_options, c_arm_intel, new_version_arm))
expect(bump_cask_pr.generate_system_options(c_arm_intel, new_version_arm))
.to eq([
[older_macos, :arm],
[:linux, :arm],
])
expect(bump_cask_pr.send(:generate_system_options, c_arm_intel, new_version_intel))
expect(bump_cask_pr.generate_system_options(c_arm_intel, new_version_intel))
.to eq([
[older_macos, :intel],
[:linux, :intel],
])
expect(bump_cask_pr.send(:generate_system_options, c_arm_intel, new_version_arm_intel))
expect(bump_cask_pr.generate_system_options(c_arm_intel, new_version_arm_intel))
.to eq([
[older_macos, :arm],
[older_macos, :intel],
[:linux, :arm],
[:linux, :intel],
])
expect(bump_cask_pr.send(:generate_system_options, c_arm_intel, new_version_intel_arm))
expect(bump_cask_pr.generate_system_options(c_arm_intel, new_version_intel_arm))
.to eq([
[older_macos, :intel],
[older_macos, :arm],
@@ -274,14 +274,14 @@ RSpec.describe Homebrew::DevCmd::BumpCaskPr do
end
it "is idempotent when the replacement has already been applied" do
bumped = bump_cask_pr.send(:replace_cask_stanza_value, contents, :version, "1.0", "2.0")
bumped = bump_cask_pr.replace_cask_stanza_value(contents, :version, "1.0", "2.0")
expect(bumped).to include('version "2.0"')
expect { bump_cask_pr.send(:replace_cask_stanza_value, bumped, :version, "1.0", "2.0") }
expect { bump_cask_pr.replace_cask_stanza_value(bumped, :version, "1.0", "2.0") }
.not_to raise_error
end
it "raises when the stanza is missing entirely" do
expect { bump_cask_pr.send(:replace_cask_stanza_value, contents, :version, "9.9", "2.0") }
expect { bump_cask_pr.replace_cask_stanza_value(contents, :version, "9.9", "2.0") }
.to raise_error(/Could not find 'version' stanza/)
end
end
@@ -317,7 +317,7 @@ RSpec.describe Homebrew::DevCmd::BumpCaskPr do
cask = Homebrew::SimulateSystem.with(os: newest_macos, arch: :arm) { cask_from_contents(contents) }
new_version = Homebrew::BumpVersionParser.new(arm: "2.0")
expect(bump_cask_pr.send(:replace_version_and_checksum, cask, new_hash, new_version, contents))
expect(bump_cask_pr.replace_version_and_checksum(cask, new_hash, new_version, contents))
.to eq <<~RUBY
cask "foo" do
on_arm do
@@ -355,7 +355,7 @@ RSpec.describe Homebrew::DevCmd::BumpCaskPr do
cask = Homebrew::SimulateSystem.with(os: newest_macos, arch: :arm) { cask_from_contents(contents) }
new_version = Homebrew::BumpVersionParser.new(arm: "2.0")
expect(bump_cask_pr.send(:replace_version_and_checksum, cask, new_hash, new_version, contents))
expect(bump_cask_pr.replace_version_and_checksum(cask, new_hash, new_version, contents))
.to eq <<~RUBY
cask "foo" do
arch arm: "arm", intel: "intel"
@@ -388,7 +388,7 @@ RSpec.describe Homebrew::DevCmd::BumpCaskPr do
cask = cask_from_contents(contents)
new_version = Homebrew::BumpVersionParser.new(arm: "2.0")
expect(bump_cask_pr.send(:replace_version_and_checksum, cask, new_hash, new_version, contents))
expect(bump_cask_pr.replace_version_and_checksum(cask, new_hash, new_version, contents))
.to eq <<~RUBY
cask "foo" do
on_arm do
@@ -419,7 +419,7 @@ RSpec.describe Homebrew::DevCmd::BumpCaskPr do
new_version = Homebrew::BumpVersionParser.new(arm: "2.0", intel: "1.5")
expect(
bump_cask_pr.send(:replace_version_and_checksum, cask, :no_check, new_version, contents),
bump_cask_pr.replace_version_and_checksum(cask, :no_check, new_version, contents),
).to eq <<~RUBY
cask "foo" do
on_arm do
@@ -462,7 +462,7 @@ RSpec.describe Homebrew::DevCmd::BumpCaskPr do
new_version = Homebrew::BumpVersionParser.new(arm: "2.0")
expect(
bump_cask_pr.send(:replace_version_and_checksum, cask, :no_check, new_version, contents),
bump_cask_pr.replace_version_and_checksum(cask, :no_check, new_version, contents),
).to eq <<~RUBY
cask "foo" do
on_arm do
@@ -502,7 +502,7 @@ RSpec.describe Homebrew::DevCmd::BumpCaskPr do
new_version = Homebrew::BumpVersionParser.new(general: "2.0")
expect(
bump_cask_pr.send(:replace_version_and_checksum, cask, new_hash, new_version, contents),
bump_cask_pr.replace_version_and_checksum(cask, new_hash, new_version, contents),
).to eq <<~RUBY
cask "foo" do
on_arm do
@@ -544,7 +544,7 @@ RSpec.describe Homebrew::DevCmd::BumpCaskPr do
new_version = Homebrew::BumpVersionParser.new(arm: "2.0")
expect(
bump_cask_pr.send(:replace_version_and_checksum, cask, :no_check, new_version, contents),
bump_cask_pr.replace_version_and_checksum(cask, :no_check, new_version, contents),
).to eq(contents)
end
end
@@ -602,14 +602,14 @@ RSpec.describe Homebrew::DevCmd::BumpCaskPr do
context "when cask is not in a tap" do
it "outputs nothing" do
expect { bump_cask_pr.send(:check_throttle, c, new_version:) }.not_to output.to_stderr
expect { bump_cask_pr.check_throttle(c, new_version:) }.not_to output.to_stderr
end
end
context "when a livecheck throttle value isn't present" do
it "does not throttle" do
allow(c).to receive(:tap).and_return(tap)
expect { bump_cask_pr.send(:check_throttle, c, new_version:) }.not_to output.to_stderr
expect { bump_cask_pr.check_throttle(c, new_version:) }.not_to output.to_stderr
end
end
@@ -623,7 +623,7 @@ RSpec.describe Homebrew::DevCmd::BumpCaskPr do
it "does not throttle" do
allow(c_throttle).to receive(:tap).and_return(tap)
expect do
bump_cask_pr.send(:check_throttle, c_throttle, new_version: empty_version)
bump_cask_pr.check_throttle(c_throttle, new_version: empty_version)
end.not_to output.to_stderr
end
end
@@ -632,7 +632,7 @@ RSpec.describe Homebrew::DevCmd::BumpCaskPr do
it "does not throttle" do
allow(c_throttle).to receive(:tap).and_return(tap)
expect do
bump_cask_pr.send(:check_throttle, c_throttle, new_version:)
bump_cask_pr.check_throttle(c_throttle, new_version:)
end.not_to output.to_stderr
end
end
@@ -643,7 +643,7 @@ RSpec.describe Homebrew::DevCmd::BumpCaskPr do
it "throttles version" do
allow(c_throttle).to receive(:tap).and_return(tap)
expect do
bump_cask_pr.send(:check_throttle, c_throttle, new_version: new_version_indivisible)
bump_cask_pr.check_throttle(c_throttle, new_version: new_version_indivisible)
rescue SystemExit
next
end.to output(throttle_error).to_stderr
@@ -661,7 +661,7 @@ RSpec.describe Homebrew::DevCmd::BumpCaskPr do
allow(Homebrew::Livecheck).to receive(:throttle_interval_elapsed?).and_return(false)
expect do
bump_cask_pr.send(:check_throttle, c_throttle_rate_and_days, new_version: new_version_indivisible)
bump_cask_pr.check_throttle(c_throttle_rate_and_days, new_version: new_version_indivisible)
rescue SystemExit
next
end.to output(throttle_rate_days_error).to_stderr
@@ -671,7 +671,7 @@ RSpec.describe Homebrew::DevCmd::BumpCaskPr do
allow(Homebrew::Livecheck).to receive(:throttle_interval_elapsed?).and_return(true)
expect do
bump_cask_pr.send(:check_throttle, c_throttle_rate_and_days, new_version: new_version_indivisible)
bump_cask_pr.check_throttle(c_throttle_rate_and_days, new_version: new_version_indivisible)
end.not_to output.to_stderr
end
end
@@ -685,7 +685,7 @@ RSpec.describe Homebrew::DevCmd::BumpCaskPr do
allow(Homebrew::Livecheck).to receive(:throttle_interval_elapsed?).and_return(false)
expect do
bump_cask_pr.send(:check_throttle, c_throttle_days, new_version:)
bump_cask_pr.check_throttle(c_throttle_days, new_version:)
rescue SystemExit
next
end.to output(throttle_days_error).to_stderr
@@ -695,7 +695,7 @@ RSpec.describe Homebrew::DevCmd::BumpCaskPr do
allow(Homebrew::Livecheck).to receive(:throttle_interval_elapsed?).and_return(true)
expect do
bump_cask_pr.send(:check_throttle, c_throttle_days, new_version:)
bump_cask_pr.check_throttle(c_throttle_days, new_version:)
end.not_to output.to_stderr
end
end
@@ -108,7 +108,7 @@ RSpec.describe Homebrew::DevCmd::BumpFormulaPr do
it "outputs nothing" do
allow(f).to receive(:tap).and_return(nil)
expect { bump_formula_pr.send(:check_throttle, f, "1.2.4") }.not_to output.to_stderr
expect { bump_formula_pr.check_throttle(f, "1.2.4") }.not_to output.to_stderr
end
end
@@ -116,7 +116,7 @@ RSpec.describe Homebrew::DevCmd::BumpFormulaPr do
it "does not throttle" do
allow(f).to receive(:tap).and_return(tap)
expect { bump_formula_pr.send(:check_throttle, f, "1.2.4") }.not_to output.to_stderr
expect { bump_formula_pr.check_throttle(f, "1.2.4") }.not_to output.to_stderr
end
end
@@ -124,7 +124,7 @@ RSpec.describe Homebrew::DevCmd::BumpFormulaPr do
it "does not throttle" do
allow(f_throttle).to receive(:tap).and_return(tap)
expect { bump_formula_pr.send(:check_throttle, f_throttle, "1.2.5") }.not_to output.to_stderr
expect { bump_formula_pr.check_throttle(f_throttle, "1.2.5") }.not_to output.to_stderr
end
end
@@ -133,7 +133,7 @@ RSpec.describe Homebrew::DevCmd::BumpFormulaPr do
allow(f_throttle).to receive(:tap).and_return(tap)
expect do
bump_formula_pr.send(:check_throttle, f_throttle, "1.2.4")
bump_formula_pr.check_throttle(f_throttle, "1.2.4")
rescue SystemExit
nil
end.to output(throttle_error).to_stderr
@@ -149,7 +149,7 @@ RSpec.describe Homebrew::DevCmd::BumpFormulaPr do
allow(Homebrew::Livecheck).to receive(:throttle_interval_elapsed?).and_return(false)
expect do
bump_formula_pr.send(:check_throttle, f_throttle_rate_and_days, "1.2.4")
bump_formula_pr.check_throttle(f_throttle_rate_and_days, "1.2.4")
rescue SystemExit
nil
end.to output(throttle_rate_days_error).to_stderr
@@ -158,7 +158,7 @@ RSpec.describe Homebrew::DevCmd::BumpFormulaPr do
it "does not throttle when throttle interval has elapsed" do
allow(Homebrew::Livecheck).to receive(:throttle_interval_elapsed?).and_return(true)
expect { bump_formula_pr.send(:check_throttle, f_throttle_rate_and_days, "1.2.4") }.not_to output.to_stderr
expect { bump_formula_pr.check_throttle(f_throttle_rate_and_days, "1.2.4") }.not_to output.to_stderr
end
end
@@ -171,7 +171,7 @@ RSpec.describe Homebrew::DevCmd::BumpFormulaPr do
allow(Homebrew::Livecheck).to receive(:throttle_interval_elapsed?).and_return(false)
expect do
bump_formula_pr.send(:check_throttle, f_throttle_days, "1.2.4")
bump_formula_pr.check_throttle(f_throttle_days, "1.2.4")
rescue SystemExit
next
end.to output(throttle_days_error).to_stderr
@@ -181,7 +181,7 @@ RSpec.describe Homebrew::DevCmd::BumpFormulaPr do
allow(Homebrew::Livecheck).to receive(:throttle_interval_elapsed?).and_return(true)
expect do
bump_formula_pr.send(:check_throttle, f_throttle_days, "1.2.4")
bump_formula_pr.check_throttle(f_throttle_days, "1.2.4")
end.not_to output.to_stderr
end
end
@@ -210,13 +210,13 @@ RSpec.describe Homebrew::DevCmd::BumpFormulaPr do
it "only updates `:parent` resource" do
expect(bump_formula_pr).to receive(:update_resource_block!).with(f, resource, version).and_return(:success)
expect(bump_formula_pr.send(:update_matching_version_resources!, f, version:)).to eq({ "parent" => :success })
expect(bump_formula_pr.update_matching_version_resources!(f, version:)).to eq({ "parent" => :success })
end
it "does not update `:parent` resource if set in `--resource-versions`" do
resource_versions = { "parent" => { current_version: "1.2.3", latest_version: version } }
expect(bump_formula_pr).not_to receive(:update_resource_block!)
expect(bump_formula_pr.send(:update_matching_version_resources!, f, version:, resource_versions:)).to eq({})
expect(bump_formula_pr.update_matching_version_resources!(f, version:, resource_versions:)).to eq({})
end
end
@@ -237,21 +237,21 @@ RSpec.describe Homebrew::DevCmd::BumpFormulaPr do
version = "2.1.0"
resource_versions = { "foo" => { current_version: "1.2.3", latest_version: version } }
expect(bump_formula_pr).to receive(:update_resource_block!).with(f, r, version).and_return(:success)
expect(bump_formula_pr.send(:update_resources!, f, resource_versions:)).to eq({ "foo" => :success })
expect(bump_formula_pr.update_resources!(f, resource_versions:)).to eq({ "foo" => :success })
end
it "downgrades to requested version" do
version = "0.1.2"
resource_versions = { "foo" => { current_version: "1.2.3", latest_version: version } }
expect(bump_formula_pr).to receive(:update_resource_block!).with(f, r, version).and_return(:success)
expect(bump_formula_pr.send(:update_resources!, f, resource_versions:)).to eq({ "foo" => :downgraded })
expect(bump_formula_pr.update_resources!(f, resource_versions:)).to eq({ "foo" => :downgraded })
end
it "returns update failures" do
version = "0.1.2"
resource_versions = { "foo" => { current_version: "1.2.3", latest_version: version } }
expect(bump_formula_pr).to receive(:update_resource_block!).with(f, r, version).and_return(:url_unchanged)
expect(bump_formula_pr.send(:update_resources!, f, resource_versions:)).to eq({ "foo" => :url_unchanged })
expect(bump_formula_pr.update_resources!(f, resource_versions:)).to eq({ "foo" => :url_unchanged })
end
end
end

Some files were not shown because too many files have changed in this diff Show More