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. 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`. 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. 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. 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. 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/rubocops/**/*"
- "Homebrew/sorbet/tapioca/**/*" - "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: Homebrew/UnreferencedLet:
Description: "Removes a lazy `let` whose name is never referenced (its block never runs)." Description: "Removes a lazy `let` whose name is never referenced (its block never runs)."
Include: 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. # 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) } 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) Formatter.redact_secrets(url.to_s, [GitHub::API.credentials].compact)
end end
+6
View File
@@ -43,6 +43,12 @@ module Homebrew
@formula_oldnames = T.let(nil, T.nilable(T::Hash[String, String])) @formula_oldnames = T.let(nil, T.nilable(T::Hash[String, String]))
end 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) } 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) def preinstall!(name, no_upgrade: false, verbose: false, **options)
new(name, options).preinstall!(no_upgrade:, verbose:) new(name, options).preinstall!(no_upgrade:, verbose:)
+15 -15
View File
@@ -102,8 +102,6 @@ module Homebrew
@pool.wait_for_termination @pool.wait_for_termination
end end
private
sig { params(entries: T::Array[Installer::InstallableEntry]).returns(T::Hash[String, T::Set[String]]) } sig { params(entries: T::Array[Installer::InstallableEntry]).returns(T::Hash[String, T::Set[String]]) }
def build_dependency_map(entries) def build_dependency_map(entries)
installed_taps = Homebrew::Bundle::Tap.installed_taps installed_taps = Homebrew::Bundle::Tap.installed_taps
@@ -177,6 +175,21 @@ module Homebrew
end end
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) } sig { params(name: String).returns(String) }
def normalize_formula_name(name) def normalize_formula_name(name)
Utils.name_from_full_name(name) Utils.name_from_full_name(name)
@@ -286,19 +299,6 @@ module Homebrew
end end
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 } sig { void }
def clear_tty_line def clear_tty_line
File.open("/dev/tty", "w") do |f| File.open("/dev/tty", "w") do |f|
+9
View File
@@ -34,6 +34,15 @@ module Homebrew
@failed_taps << tap_name @failed_taps << tap_name
end 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 private
sig { returns(T::Hash[Symbol, T.nilable(T::Array[String])]) } sig { returns(T::Hash[Symbol, T.nilable(T::Array[String])]) }
+3
View File
@@ -133,6 +133,9 @@ class CacheStoreDatabase
db.each_key(&block) db.each_key(&block)
end end
sig { params(db: T.nilable(T::Hash[Key, Value])).void }
attr_writer :db
private private
# Lazily loaded database in read/write mode. If this method is called, a # 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])) @bundle_ids_to_reopen ||= T.let([], T.nilable(T::Array[String]))
end 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 private
sig { params(options: DirectivesType).void } sig { params(options: DirectivesType).void }
@@ -189,17 +283,6 @@ module Cask
end 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
sig { returns(String) } sig { returns(String) }
def automation_access_instructions def automation_access_instructions
<<~EOS <<~EOS
@@ -209,47 +292,6 @@ module Cask
EOS EOS
end 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) } sig { params(bundle_id: String).returns(T::Boolean) }
def running?(bundle_id) def running?(bundle_id)
script = <<~JAVASCRIPT script = <<~JAVASCRIPT
@@ -452,48 +494,6 @@ module Cask
end end
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 } sig { params(paths: T.any(Pathname, String), command: T.class_of(SystemCommand), _kwargs: T.anything).void }
def uninstall_delete(*paths, command:, **_kwargs) def uninstall_delete(*paths, command:, **_kwargs)
return if paths.empty? return if paths.empty?
+5 -5
View File
@@ -56,6 +56,11 @@ module Cask
end end
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 private
sig { sig {
@@ -259,11 +264,6 @@ module Cask
def undeletable?(target) def undeletable?(target)
!target.parent.writable? !target.parent.writable?
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
end end
end end
end end
+5 -5
View File
@@ -80,11 +80,6 @@ module Cask
"#{@source_string}#{target_string}" "#{@source_string}#{target_string}"
end 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 # Try to make the asset searchable under the target name. Spotlight
# respects this attribute for many filetypes, but ignores it for App # respects this attribute for many filetypes, but ignores it for App
# bundles. Alfred 2.2 respects it even for App bundles. # bundles. Alfred 2.2 respects it even for App bundles.
@@ -112,6 +107,11 @@ module Cask
sudo: !file.writable?) sudo: !file.writable?)
end end
private
ALT_NAME_ATTRIBUTE = "com.apple.metadata:kMDItemAlternateNames"
private_constant :ALT_NAME_ATTRIBUTE
sig { returns(String) } sig { returns(String) }
def printable_target def printable_target
target.to_s.sub(/^#{Dir.home}(#{File::SEPARATOR}|$)/, "~/") target.to_s.sub(/^#{Dir.home}(#{File::SEPARATOR}|$)/, "~/")
+124 -121
View File
@@ -36,6 +36,9 @@ module Cask
sig { returns(T.nilable(Download)) } sig { returns(T.nilable(Download)) }
attr_reader :download attr_reader :download
sig { params(livecheck_result: T.nilable(T.any(T::Boolean, Symbol))).void }
attr_writer :livecheck_result
sig { sig {
params( params(
cask: ::Cask::Cask, download: T::Boolean, quarantine: T::Boolean, cask: ::Cask::Cask, download: T::Boolean, quarantine: T::Boolean,
@@ -148,6 +151,127 @@ module Cask
summary.join("\n") summary.join("\n")
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 { 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 private
sig { void } sig { void }
@@ -562,107 +686,6 @@ module Cask
end end
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 } sig { void }
def audit_rosetta def audit_rosetta
return if (url = cask.url).nil? return if (url = cask.url).nil?
@@ -955,26 +978,6 @@ module Cask
normalize_min_os(min_os) normalize_min_os(min_os)
end 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)) } sig { params(path: Pathname).returns(T.nilable(String)) }
def get_plist_main_binary(path) def get_plist_main_binary(path)
return unless online? return unless online?
+2 -2
View File
@@ -102,8 +102,6 @@ module Cask
errors errors
end end
private
sig { params(audit: T.nilable(Audit)).returns(T::Boolean) } sig { params(audit: T.nilable(Audit)).returns(T::Boolean) }
def output_summary?(audit = nil) def output_summary?(audit = nil)
return true if @any_named_args return true if @any_named_args
@@ -113,6 +111,8 @@ module Cask
audit.errors? audit.errors?
end end
private
sig { params(languages: T::Array[String]).returns(::Cask::Audit) } sig { params(languages: T::Array[String]).returns(::Cask::Audit) }
def audit_languages(languages) def audit_languages(languages)
original_config = cask.config original_config = cask.config
+3
View File
@@ -147,6 +147,9 @@ module Cask
sig { returns(Pathname) } sig { returns(Pathname) }
attr_reader :path 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 } sig { params(path: T.any(Pathname, String), token: String).void }
def initialize(path, token: T.unsafe(nil)) def initialize(path, token: T.unsafe(nil))
super() super()
+7
View File
@@ -205,6 +205,13 @@ module Cask
path.stat.gid == group.gid path.stat.gid == group.gid
end 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) } sig { returns(String) }
def self.expected_caskroom_group def self.expected_caskroom_group
"admin" "admin"
+3 -3
View File
@@ -229,6 +229,9 @@ module Cask
sig { override.returns(String) } sig { override.returns(String) }
def download_queue_type = "Cask" def download_queue_type = "Cask"
sig { override.returns(String) }
def download_name = cask.token
private private
sig { void } sig { void }
@@ -280,8 +283,5 @@ module Cask
def cache def cache
Cache.path Cache.path
end end
sig { override.returns(String) }
def download_name = cask.token
end end
end end
+3
View File
@@ -56,6 +56,9 @@ module Homebrew
sig { returns(T::Array[Subcommand]) } sig { returns(T::Array[Subcommand]) }
attr_reader :subcommands attr_reader :subcommands
sig { returns(T.nilable(Integer)) }
attr_reader :min_named_args
sig { params(cmd_path: Pathname).returns(T.nilable(CLI::Parser)) } sig { params(cmd_path: Pathname).returns(T.nilable(CLI::Parser)) }
def self.from_cmd_path(cmd_path) def self.from_cmd_path(cmd_path)
cmd_args_method_name = Commands.args_method_name(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 download_queue.shutdown
end 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 private
sig { returns(T::Boolean) } sig { returns(T::Boolean) }
@@ -288,68 +350,6 @@ module Homebrew
names names
end 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) } sig { returns(Integer) }
def retries def retries
@retries ||= T.let(args.retry? ? FETCH_MAX_TRIES : 1, T.nilable(Integer)) @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, private_class_method :formula_metadata_lines, :formatted_time, :pin_path_mtime,
:formula_installs_from_source?, :cask_requirements_lines :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 } sig { params(quiet: T::Boolean).void }
def print_info(quiet: false) def print_info(quiet: false)
objects = args.named.to_formulae_and_casks_and_unavailable(uniq: false) objects = args.named.to_formulae_and_casks_and_unavailable(uniq: false)
@@ -411,25 +378,6 @@ module Homebrew
end 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), qualified_inputs: T::Set[String]).returns(T::Boolean) } 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) def formula_qualified_by_user?(formula_or_cask, qualified_inputs)
return false if qualified_inputs.empty? return false if qualified_inputs.empty?
@@ -441,24 +389,6 @@ module Homebrew
names.any? { |n| qualified_inputs.include?(n) } names.any? { |n| qualified_inputs.include?(n) }
end 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)]) } sig { params(formula: Formula).returns([Formula, T.nilable(Tap)]) }
def installed_resolution(formula) def installed_resolution(formula)
keg = formula.installed_kegs.last keg = formula.installed_kegs.last
@@ -472,95 +402,6 @@ module Homebrew
[formula, nil] [formula, nil]
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(formula_or_cask: T.any(Formula, Cask::Cask)).returns(String) } sig { params(formula_or_cask: T.any(Formula, Cask::Cask)).returns(String) }
def github_info(formula_or_cask) def github_info(formula_or_cask)
tap = T.let(nil, T.nilable(Tap)) tap = T.let(nil, T.nilable(Tap))
@@ -594,13 +435,6 @@ module Homebrew
github_remote_path(remote, path.to_s) github_remote_path(remote, path.to_s)
end 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 } sig { params(formula: Formula).void }
def info_formula_summary(formula) def info_formula_summary(formula)
kegs = formula.installed_kegs kegs = formula.installed_kegs
@@ -830,6 +664,172 @@ module Homebrew
Utils::Analytics.formula_output(formula, args:) Utils::Analytics.formula_output(formula, args:)
end 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]) } sig { params(formula: Formula, verbose: T::Boolean).returns(T::Array[String]) }
def installed_section_lines(formula, verbose: false) def installed_section_lines(formula, verbose: false)
siblings = formula.versioned_formulae siblings = formula.versioned_formulae
+25 -25
View File
@@ -90,6 +90,31 @@ module Homebrew
Homebrew.failed = args.named.present? && outdated.present? Homebrew.failed = args.named.present? && outdated.present?
end 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 private
sig { params(formulae_or_casks: T::Array[T.any(Formula, Cask::Cask)]).void } 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)] [select_outdated(formulae).sort, select_outdated(casks)]
end 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]) } sig { params(formula: Formula).returns(T::Array[Keg]) }
def formula_outdated_kegs(formula) def formula_outdated_kegs(formula)
MinimumVersion.formula_outdated_kegs(formula, minimum_version, fetch_head: args.fetch_HEAD?) 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 print_regex_help
end 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 private
sig { void } sig { void }
@@ -154,21 +169,6 @@ module Homebrew
odie "No formulae or casks found for #{query.inspect}." if count.zero? odie "No formulae or casks found for #{query.inspect}." if count.zero?
end 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 end
end end
+20 -20
View File
@@ -45,26 +45,6 @@ module Homebrew
exec_browser(*repo_urls) exec_browser(*repo_urls)
end 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)) } sig { params(url: String).returns(T.nilable(String)) }
def url_to_repo(url) def url_to_repo(url)
github_repo_url(url) || github_repo_url(url) ||
@@ -207,6 +187,26 @@ module Homebrew
rescue JSON::ParserError rescue JSON::ParserError
nil nil
end 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 end
end end
+31 -31
View File
@@ -40,8 +40,6 @@ module Homebrew
end end
end end
private
sig { params(taps: T::Array[Tap]).void } sig { params(taps: T::Array[Tap]).void }
def print_tap_info(taps) def print_tap_info(taps)
if taps.none? if taps.none?
@@ -95,9 +93,6 @@ module Homebrew
end end
end end
LISTING_LIMIT = 30
private_constant :LISTING_LIMIT
sig { params(tap: Tap).void } sig { params(tap: Tap).void }
def print_tap_listings(tap) def print_tap_listings(tap)
commands = tap.command_files commands = tap.command_files
@@ -124,32 +119,6 @@ module Homebrew
end end
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) } sig { params(tap: Tap, name: String, installed: T::Boolean).returns(String) }
def decorate_formula(tap, name, installed:) def decorate_formula(tap, name, installed:)
formula = Formulary.factory("#{tap.name}/#{name}") formula = Formulary.factory("#{tap.name}/#{name}")
@@ -187,6 +156,37 @@ module Homebrew
puts JSON.pretty_generate(hashes) puts JSON.pretty_generate(hashes)
end 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 end
end end
+11 -11
View File
@@ -50,6 +50,17 @@ module Homebrew
end end
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 private
sig { void } sig { void }
@@ -405,17 +416,6 @@ module Homebrew
Utils::Analytics.messages_displayed! if $stdout.tty? Utils::Analytics.messages_displayed! if $stdout.tty?
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
sig { void } sig { void }
def install_from_api_message def install_from_api_message
return if Settings.read("installfromapimessage") == "true" return if Settings.read("installfromapimessage") == "true"
+29 -29
View File
@@ -338,8 +338,6 @@ class Reporter
end end
end end
private
sig { params(name: String, new_name: String, new_tap: Tap).returns(T::Boolean) } sig { params(name: String, new_name: String, new_tap: Tap).returns(T::Boolean) }
def ensure_trusted_tap_installed!(name, new_name, new_tap) def ensure_trusted_tap_installed!(name, new_name, new_tap)
return true if new_tap.installed? return true if new_tap.installed?
@@ -368,33 +366,6 @@ class Reporter
true true
end 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) } sig { returns(String) }
def diff def diff
@diff ||= T.let(nil, T.nilable(String)) @diff ||= T.let(nil, T.nilable(String))
@@ -436,4 +407,33 @@ class Reporter
) )
end end
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 end
@@ -20,6 +20,11 @@ class ReporterHub
T.cast(@hash.fetch(key, []), T::Array[String]) T.cast(@hash.fetch(key, []), T::Array[String])
end 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 } sig { params(reporter: Reporter, auto_update: T::Boolean).void }
def add(reporter, auto_update: false) def add(reporter, auto_update: false)
@reporters << reporter @reporters << reporter
+79 -79
View File
@@ -336,51 +336,6 @@ module Homebrew
show_final_upgrade_summary show_final_upgrade_summary
end 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 { sig {
params(formulae: T::Array[Formula], show_upgrade_summary: T::Boolean, params(formulae: T::Array[Formula], show_upgrade_summary: T::Boolean,
dry_run: T::Boolean).returns(T.nilable(FormulaeUpgradeContext)) dry_run: T::Boolean).returns(T.nilable(FormulaeUpgradeContext))
@@ -624,15 +579,6 @@ module Homebrew
end 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(formulae: T::Array[Formula], include_sizes: T::Boolean).returns(T::Array[String]) } sig { params(formulae: T::Array[Formula], include_sizes: T::Boolean).returns(T::Array[String]) }
def formula_upgrade_descriptions(formulae, include_sizes: false) def formula_upgrade_descriptions(formulae, include_sizes: false)
formulae.map do |formula| formulae.map do |formula|
@@ -654,31 +600,6 @@ module Homebrew
end end
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 { sig {
params( params(
formulae: T::Array[Formula], formulae: T::Array[Formula],
@@ -919,6 +840,85 @@ module Homebrew
ofail e ofail e
false false
end 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 end
end end
+3
View File
@@ -163,6 +163,9 @@ class Dependency
end end
class << self 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 # Expand the dependencies of each dependent recursively, optionally yielding
# `[dependent, dep]` pairs to allow callers to apply arbitrary filters to # `[dependent, dep]` pairs to allow callers to apply arbitrary filters to
# the list. # the list.
+57 -57
View File
@@ -216,17 +216,6 @@ module Homebrew
end end
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]]) } sig { params(cask: Cask::Cask, new_version: BumpVersionParser).returns(T::Array[[Symbol, Symbol]]) }
def generate_system_options(cask, new_version) def generate_system_options(cask, new_version)
current_os = Homebrew::SimulateSystem.current_os current_os = Homebrew::SimulateSystem.current_os
@@ -375,6 +364,63 @@ module Homebrew
contents contents
end 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 { sig {
params( params(
new_version: BumpVersionParser, new_version: BumpVersionParser,
@@ -434,52 +480,6 @@ module Homebrew
nil nil
end 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 } sig { params(cask: Cask::Cask, new_version: BumpVersionParser).void }
def check_pull_requests(cask, new_version:) def check_pull_requests(cask, new_version:)
tap = cask.tap tap = cask.tap
+82 -82
View File
@@ -504,6 +504,88 @@ module Homebrew
end end
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 private
sig { params(url: String).returns(T.nilable(String)) } 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? check_pull_requests(formula, tap_remote_repo, version:) unless args.write_only?
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, new_formula_version: Version).returns(T.nilable(T::Array[String])) } sig { params(formula: Formula, new_formula_version: Version).returns(T.nilable(T::Array[String])) }
def alias_update_pair(formula, new_formula_version) def alias_update_pair(formula, new_formula_version)
versioned_alias = formula.aliases.grep(/^.*@\d+(\.\d+)?$/).first versioned_alias = formula.aliases.grep(/^.*@\d+(\.\d+)?$/).first
@@ -733,65 +792,6 @@ module Homebrew
:success :success
end 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 { sig {
params(formula: Formula, alias_rename: T.nilable(T::Array[String]), params(formula: Formula, alias_rename: T.nilable(T::Array[String]),
skip_synced_versions: T::Boolean).returns(T::Boolean) skip_synced_versions: T::Boolean).returns(T::Boolean)
+234 -234
View File
@@ -184,62 +184,6 @@ module Homebrew
end 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 { sig {
params(formula_or_cask: T.any(Formula, Cask::Cask)).returns(T::Boolean) params(formula_or_cask: T.any(Formula, Cask::Cask)).returns(T::Boolean)
} }
@@ -268,86 +212,6 @@ module Homebrew
true true
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 { sig {
params( params(
formula_or_cask: T.any(Formula, Cask::Cask), formula_or_cask: T.any(Formula, Cask::Cask),
@@ -527,68 +391,6 @@ module Homebrew
) )
end 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 { sig {
params( params(
formula_or_cask: T.any(Formula, Cask::Cask), formula_or_cask: T.any(Formula, Cask::Cask),
@@ -876,42 +678,6 @@ module Homebrew
value.match?(LIVECHECK_MESSAGE_REGEX) value.match?(LIVECHECK_MESSAGE_REGEX)
end 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 # Identifies the highest upstream version that has been released before
# the cooldown interval. # the cooldown interval.
# #
@@ -1028,6 +794,240 @@ module Homebrew
end end
end 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 end
end end
+152 -152
View File
@@ -227,8 +227,6 @@ module Homebrew
puts generate_csv(grand_totals) puts generate_csv(grand_totals)
end end
private
sig { sig {
params(repository_refs: T::Hash[String, [Pathname, String]], to: String) 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)]]) .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] [user_names, lead_maintainers, maintainer_since_dates]
end 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)) } sig { params(repository_path: Pathname, ref: String, user: String, name: String).returns(T.nilable(String)) }
def maintainer_since(repository_path, ref, user, name) def maintainer_since(repository_path, ref, user, name)
require "utils/git" require "utils/git"
@@ -406,12 +292,6 @@ module Homebrew
nil nil
end 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 { sig {
params( params(
organisation: String, organisation: String,
@@ -559,38 +439,6 @@ module Homebrew
results results
end 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)) } sig { params(user: String, to: String).returns(T.nilable(String)) }
def github_username_for(user, to:) def github_username_for(user, to:)
return user unless user.include?("@") return user unless user.include?("@")
@@ -731,6 +579,158 @@ module Homebrew
counts counts
end 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 { sig {
params(name: String, email: String, identity_users: T::Hash[String, String]).returns(T.nilable(String)) params(name: String, email: String, identity_users: T::Hash[String, String]).returns(T.nilable(String))
} }
@@ -192,6 +192,28 @@ module Homebrew
end end
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 private
sig { params(cask: Cask::Cask, os: Symbol).returns(T::Array[Symbol]) } sig { params(cask: Cask::Cask, os: Symbol).returns(T::Array[Symbol]) }
@@ -223,28 +245,6 @@ module Homebrew
max_runner.first max_runner.first
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
sig { sig {
params(tap: T.nilable(Tap), labels: T::Array[String], cask_names: T::Array[String], skip_install: T::Boolean, 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, 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) puts format_stanza(trash: trash_paths, delete: delete_paths, rmdir: rmdir_paths)
end end
private
sig { params(cask: Cask::Cask).returns(T::Array[String]) } sig { params(cask: Cask::Cask).returns(T::Array[String]) }
def resolve_patterns_from_cask(cask) def resolve_patterns_from_cask(cask)
app_artifact = cask.artifacts.find { |a| a.is_a?(Cask::Artifact::App) } app_artifact = cask.artifacts.find { |a| a.is_a?(Cask::Artifact::App) }
@@ -144,11 +142,6 @@ module Homebrew
end end
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]) } sig { params(app_artifact: Cask::Artifact::App).returns(T::Array[String]) }
def bundle_identifiers(app_artifact) def bundle_identifiers(app_artifact)
info_plist = app_artifact.target/"Contents/Info.plist" info_plist = app_artifact.target/"Contents/Info.plist"
@@ -238,35 +231,6 @@ module Homebrew
result.uniq.sort result.uniq.sort
end 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]) } sig { params(paths: T::Array[String]).returns(T::Array[String]) }
def replace_uuids(paths) def replace_uuids(paths)
paths.map { |p| p.gsub(UUID_PATTERN, "*") }.uniq.sort paths.map { |p| p.gsub(UUID_PATTERN, "*") }.uniq.sort
@@ -321,6 +285,42 @@ module Homebrew
.prepend("zap ") .prepend("zap ")
end 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) } sig { params(key: String, paths: T::Array[String]).returns(String) }
def format_directive(key, paths) def format_directive(key, paths)
if paths.size == 1 if paths.size == 1
+2 -2
View File
@@ -215,8 +215,6 @@ module Homebrew
exec_browser release_url exec_browser release_url
end end
private
sig { params(name: String).returns(T::Array[T::Hash[String, T.untyped]]) } sig { params(name: String).returns(T::Array[T::Hash[String, T.untyped]]) }
def matching_releases(name) def matching_releases(name)
releases_url = "#{GitHub::API_URL}/repos/Homebrew/brew/releases?per_page=#{GitHub::MAX_PER_PAGE}" releases_url = "#{GitHub::API_URL}/repos/Homebrew/brew/releases?per_page=#{GitHub::MAX_PER_PAGE}"
@@ -240,6 +238,8 @@ module Homebrew
end end
end end
private
sig { params(releases: T::Array[T::Hash[String, T.untyped]]).returns(T::Array[String]) } sig { params(releases: T::Array[T::Hash[String, T.untyped]]).returns(T::Array[String]) }
def release_urls(releases) def release_urls(releases)
releases.filter_map do |release| releases.filter_map do |release|
+38 -38
View File
@@ -264,47 +264,9 @@ module Homebrew
end end
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 } sig { void }
def check_test_environment!; end 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]) } sig { returns(T::Array[String]) }
def changed_test_files def changed_test_files
changed_files = Utils::Git.changed_files(HOMEBREW_REPOSITORY) changed_files = Utils::Git.changed_files(HOMEBREW_REPOSITORY)
@@ -331,6 +293,44 @@ module Homebrew
.map(&:to_s) .map(&:to_s)
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 { 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]) } sig { params(filestub: String).returns(T::Array[Pathname]) }
def shared_context_test_files(filestub) def shared_context_test_files(filestub)
case filestub case filestub
+5 -5
View File
@@ -268,6 +268,11 @@ module Homebrew
pool.wait_for_termination pool.wait_for_termination
end end
sig { returns(T::Hash[Downloadable, Concurrent::Promises::Future]) }
def downloads
@downloads ||= T.let({}, T.nilable(T::Hash[Downloadable, Concurrent::Promises::Future]))
end
private private
sig { params(downloadable: Downloadable, check_attestation: T::Boolean).void } sig { params(downloadable: Downloadable, check_attestation: T::Boolean).void }
@@ -344,11 +349,6 @@ module Homebrew
tty && !@dumb_tty tty && !@dumb_tty
end 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)) } sig { params(future: Concurrent::Promises::Future).returns(T.nilable(String)) }
def status_from_future(future) def status_from_future(future)
case future.state 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 FileUtils.ln_s target_cached_location.relative_path_from(symlink_location.dirname), symlink_location, force: true
end 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) } sig { params(url: String, search_query: T::Boolean).returns(String) }
def parse_basename(url, search_query: true) def parse_basename(url, search_query: true)
components = { path: T.let([], T::Array[String]), query: T.let([], T::Array[String]) } components = { path: T.let([], T::Array[String]), query: T.let([], T::Array[String]) }
@@ -125,4 +110,19 @@ class AbstractFileDownloadStrategy < AbstractDownloadStrategy
File.basename(filename) File.basename(filename)
end 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 end
@@ -146,6 +146,32 @@ class CurlDownloadStrategy < AbstractFileDownloadStrategy
[time, T.must(file_size)] [time, T.must(file_size)]
end 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 private
sig { params(timeout: T.nilable(T.any(Float, Integer))).returns([String, String]) } 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: curl_download resolved_url, to:, try_partial: @try_partial, timeout:
end end
sig { void }
def allow_deferred_environment_expansion!
@expand_deferred_environment = true
end
sig { params(args: T::Array[String]).returns(T::Array[String]) } sig { params(args: T::Array[String]).returns(T::Array[String]) }
def expand_deferred_environment_args(args) def expand_deferred_environment_args(args)
return args unless @expand_deferred_environment return args unless @expand_deferred_environment
@@ -276,27 +297,6 @@ class CurlDownloadStrategy < AbstractFileDownloadStrategy
end end
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)]) } sig { returns(T::Hash[Symbol, T.any(String, Symbol)]) }
def _curl_opts def _curl_opts
meta.slice(:user_agent) meta.slice(:user_agent)
@@ -49,6 +49,30 @@ class GitDownloadStrategy < VCSDownloadStrategy
@last_commit || "" @last_commit || ""
end 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 private
# Read user Git config so credential helpers work for private downloads, # Read user Git config so credential helpers work for private downloads,
@@ -101,14 +125,6 @@ class GitDownloadStrategy < VCSDownloadStrategy
cached_location/".git" cached_location/".git"
end 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) } sig { override.returns(String) }
def current_revision def current_revision
system_command("git", args: ["--git-dir", git_dir, "rev-parse", "-q", "--verify", "HEAD"], 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? Utils::Git.supports_partial_clone_sparse_checkout?
end 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) } sig { returns(String) }
def refspec def refspec
case @ref_type case @ref_type
@@ -5,6 +5,12 @@
# #
# @api public # @api public
class GitHubGitDownloadStrategy < GitDownloadStrategy 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 } sig { params(url: String, name: String, version: T.nilable(Version), meta: T.untyped).void }
def initialize(url, name, version, **meta) def initialize(url, name, version, **meta)
super super
+3 -2
View File
@@ -187,8 +187,9 @@ module Downloadable
download_strategy.new(primary_url, download_name, version, download_strategy.new(primary_url, download_name, version,
mirrors:, cache:, **T.must(@url).specs).tap do |downloader| mirrors:, cache:, **T.must(@url).specs).tap do |downloader|
if AbstractDownloadStrategy.expand_deferred_environment_for?(downloader) if downloader.is_a?(CurlDownloadStrategy) &&
downloader.send(:allow_deferred_environment_expansion!) AbstractDownloadStrategy.expand_deferred_environment_for?(downloader)
downloader.allow_deferred_environment_expansion!
end end
end end
end end
@@ -41,6 +41,11 @@ module OS
Dependency.new(GLIBC, [:implicit]) Dependency.new(GLIBC, [:implicit])
end end
sig { returns(T::Hash[String, T::Set[String]]) }
def global_dep_tree
@@global_dep_tree
end
private private
GLIBC = "glibc" GLIBC = "glibc"
@@ -114,11 +119,6 @@ module OS
@@global_dep_tree = T.let({}, T::Hash[String, T::Set[String]]) @@global_dep_tree = T.let({}, T::Hash[String, T::Set[String]])
@@building_global_dep_tree = T.let(false, T::Boolean) @@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 } sig { void }
def building_global_dep_tree! def building_global_dep_tree!
@@building_global_dep_tree = true @@building_global_dep_tree = true
@@ -11,18 +11,6 @@ module OS
requires_ancestor { Homebrew::DevCmd::Tests } 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 } sig { void }
def check_test_environment! def check_test_environment!
super super
@@ -37,6 +25,18 @@ module OS
end end
::Sandbox.ensure_sandbox_available! ::Sandbox.ensure_sandbox_available!
end 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 end
end end
+7 -7
View File
@@ -176,13 +176,6 @@ module OS
implementation.run { super } implementation.run { super }
end 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]) } sig { params(tmpdir: String).returns(T::Array[String]) }
def bubblewrap_args(tmpdir) def bubblewrap_args(tmpdir)
bubblewrap.arguments(tmpdir) bubblewrap.arguments(tmpdir)
@@ -193,6 +186,13 @@ module OS
bubblewrap.writable_paths bubblewrap.writable_paths
end 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 } sig { void }
def apply_sandbox def apply_sandbox
sandbox = implementation sandbox = implementation
@@ -369,8 +369,6 @@ class Sandbox
end end
end end
private
sig { params(abi: Integer).returns([String, Integer, Integer]) } sig { params(abi: Integer).returns([String, Integer, Integer]) }
def ruleset_attributes(abi) def ruleset_attributes(abi)
allowed_access_fs = WRITE_ACCESS_FS allowed_access_fs = WRITE_ACCESS_FS
@@ -397,6 +395,17 @@ class Sandbox
[attributes.pack("Q*"), handled_access_fs, allowed_access_fs] [attributes.pack("Q*"), handled_access_fs, allowed_access_fs]
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
private
sig { params(ruleset_fd: Integer, path: String, allowed_access: Integer).void } sig { params(ruleset_fd: Integer, path: String, allowed_access: Integer).void }
def add_path_rule(ruleset_fd, path, allowed_access) def add_path_rule(ruleset_fd, path, allowed_access)
path_fd = open_path(path) path_fd = open_path(path)
@@ -424,15 +433,6 @@ class Sandbox
end end
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 } sig { params(path: ::Pathname, denied_paths: T::Array[::Pathname], paths: T::Array[String]).void }
def add_readable_path(path, denied_paths, paths) def add_readable_path(path, denied_paths, paths)
return if denied_paths.include?(path) return if denied_paths.include?(path)
@@ -20,8 +20,6 @@ module OS
requires_ancestor { Utils::Bottles::Collector } requires_ancestor { Utils::Bottles::Collector }
private
sig { sig {
params(tag: Utils::Bottles::Tag, params(tag: Utils::Bottles::Tag,
no_older_versions: T::Boolean).returns(T.nilable(Utils::Bottles::Tag)) no_older_versions: T::Boolean).returns(T.nilable(Utils::Bottles::Tag))
@@ -38,6 +36,8 @@ module OS
end end
end end
private
# Find a bottle built for a previous version of macOS. # Find a bottle built for a previous version of macOS.
sig { params(tag: Utils::Bottles::Tag).returns(T.nilable(Utils::Bottles::Tag)) } sig { params(tag: Utils::Bottles::Tag).returns(T.nilable(Utils::Bottles::Tag)) }
def find_older_compatible_tag(tag) def find_older_compatible_tag(tag)
+26 -25
View File
@@ -150,7 +150,7 @@ class Formula
# #
# @api public # @api public
sig { returns(T.nilable(Tap)) } sig { returns(T.nilable(Tap)) }
attr_reader :tap attr_accessor :tap
# The stable (and default) {SoftwareSpec} for this {Formula}. # The stable (and default) {SoftwareSpec} for this {Formula}.
# This contains all the attributes (e.g. URL, checksum) that apply to the # This contains all the attributes (e.g. URL, checksum) that apply to the
@@ -176,8 +176,6 @@ class Formula
sig { returns(SoftwareSpec) } sig { returns(SoftwareSpec) }
attr_reader :active_spec attr_reader :active_spec
protected :active_spec
# A symbol to indicate currently active {SoftwareSpec}. # A symbol to indicate currently active {SoftwareSpec}.
# It's either `:stable` or `:head`. # It's either `:stable` or `:head`.
# @see #active_spec # @see #active_spec
@@ -215,7 +213,7 @@ class Formula
# #
# @api public # @api public
sig { returns(T.nilable(Pathname)) } sig { returns(T.nilable(Pathname)) }
attr_reader :buildpath attr_accessor :buildpath
# The current working directory during tests. # The current working directory during tests.
# Will only be non-`nil` inside {.test}. # Will only be non-`nil` inside {.test}.
@@ -882,6 +880,9 @@ class Formula
end end
end end
sig { params(oldnames: T.nilable(T::Array[String])).void }
attr_writer :oldnames
# All aliases for the formula. # All aliases for the formula.
# #
# @api internal # @api internal
@@ -3669,6 +3670,27 @@ class Formula
T.must(bottle).tab_attributes T.must(bottle).tab_attributes
end 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 private
sig { void } sig { void }
@@ -3715,27 +3737,6 @@ class Formula
exit! 1 # never gets here unless exec threw or failed exit! 1 # never gets here unless exec threw or failed
end 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 } 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) def stage(interactive: false, debug_symbols: false, &_block)
active_spec.stage(debug_symbols:) do |staging| active_spec.stage(debug_symbols:) do |staging|
+51 -51
View File
@@ -1236,57 +1236,6 @@ module Homebrew
end end
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]) } sig { params(tap: Tap, only_names: T::Array[String]).returns(T::Array[Pathname]) }
def changed_formulae_paths(tap, only_names: [].freeze) def changed_formulae_paths(tap, only_names: [].freeze)
return [] unless tap.git? return [] unless tap.git?
@@ -1369,6 +1318,57 @@ module Homebrew
@committed_version_info_cache[formula.full_name] = [previous_version_info, base_ref_version_info] @committed_version_info_cache[formula.full_name] = [previous_version_info, base_ref_version_info]
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).returns(String) } sig { params(tap: Tap).returns(String) }
def git_audit_base_ref(tap) def git_audit_base_ref(tap)
@git_audit_base_ref_cache ||= T.let({}, T.nilable(T::Hash[Pathname, T.nilable(String)])) @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) } sig { returns(Homebrew::DownloadQueue) }
attr_accessor :download_queue attr_accessor :download_queue
sig { params(ran_prelude: T::Boolean).void }
attr_writer :ran_prelude
sig { sig {
params( params(
formula: Formula, formula: Formula,
+143 -143
View File
@@ -141,149 +141,6 @@ class GitHubPackages
version_rebuild version_rebuild
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 { sig {
params( params(
user: String, token: String, skopeo: Pathname, formula_full_name: String, user: String, token: String, skopeo: Pathname, formula_full_name: String,
@@ -518,6 +375,149 @@ class GitHubPackages
end end
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]) } sig { params(root: Pathname).returns([String, Integer]) }
def write_image_layout(root) def write_image_layout(root)
image_layout = { imageLayoutVersion: "1.0.0" } image_layout = { imageLayoutVersion: "1.0.0" }
+80 -80
View File
@@ -108,86 +108,6 @@ class GitHubRunnerMatrix
end end
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 } sig { void }
def generate_runners! def generate_runners!
return if @runners.present? return if @runners.present?
@@ -284,6 +204,86 @@ class GitHubRunnerMatrix
@runners.freeze @runners.freeze
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 { params(runner: GitHubRunner).returns(T::Array[String]) } sig { params(runner: GitHubRunner).returns(T::Array[String]) }
def testable_formulae(runner) def testable_formulae(runner)
formulae = if @dependent_matrix formulae = if @dependent_matrix
+10 -10
View File
@@ -720,6 +720,16 @@ module Homebrew
ask_input(action:) ask_input(action:)
end 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 private
sig { params(action: String).returns(String) } sig { params(action: String).returns(String) }
@@ -741,16 +751,6 @@ module Homebrew
.map { |k| Keg.new(k.resolved_path) } .map { |k| Keg.new(k.resolved_path) }
end 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 } sig { void }
def attempt_directory_creation def attempt_directory_creation
Keg.must_exist_directories.each do |dir| Keg.must_exist_directories.each do |dir|
+5
View File
@@ -12,6 +12,11 @@ module Language
module Node module Node
extend ::Utils::Output::Mixin extend ::Utils::Output::Mixin
class << self
sig { returns(T.nilable(T::Boolean)) }
attr_accessor :env_set
end
sig { returns(String) } sig { returns(String) }
def self.npm_cache_config def self.npm_cache_config
"cache=#{HOMEBREW_CACHE}/npm_cache" "cache=#{HOMEBREW_CACHE}/npm_cache"
+4 -4
View File
@@ -34,13 +34,13 @@ module Homebrew
private_constant :UNSTABLE_VERSION_KEYWORDS private_constant :UNSTABLE_VERSION_KEYWORDS
sig { params(strategy_class: T::Class[Strategic]).returns(String) } 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 ||= T.let({}, T.nilable(T::Hash[T::Class[Strategic], String]))
@livecheck_strategy_names[strategy_class] ||= Utils.demodulize(strategy_class.name) @livecheck_strategy_names[strategy_class] ||= Utils.demodulize(strategy_class.name)
end end
sig { params(strategy_class: T::Class[Strategic]).returns(T::Array[Symbol]) } 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 ||= T.let({}, T.nilable(T::Hash[T::Class[Strategic], T::Array[Symbol]]))
@livecheck_find_versions_parameters[strategy_class] ||= @livecheck_find_versions_parameters[strategy_class] ||=
(T::Utils.signature_for_method(strategy_class.method(:find_versions))&.parameters || (T::Utils.signature_for_method(strategy_class.method(:find_versions))&.parameters ||
@@ -1133,7 +1133,7 @@ module Homebrew
end end
sig { params(package_or_resource: T.any(Formula, Cask::Cask)).returns(T.nilable(Integer)) } 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 tap = package_or_resource.tap
return if tap.nil? return if tap.nil?
return unless tap.git? return unless tap.git?
@@ -1261,7 +1261,7 @@ module Homebrew
end end
sig { params(package_or_resource: T.any(Formula, Cask::Cask), days: Integer).returns(T::Boolean) } 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 return false if days <= 0
last_updated_timestamp = formula_or_cask_last_updated_timestamp(package_or_resource) 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. # Used to cache processed URLs, to avoid duplicating effort.
@processed_urls = T.let({}, T::Hash[String, String]) @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 # The priority of the strategy on an informal scale of 1 to 10 (from
# lowest to highest). # lowest to highest).
PRIORITY = 8 PRIORITY = 8
@@ -63,6 +63,11 @@ module Homebrew
# repeatedly. # repeatedly.
@page_data = T.let({}, T::Hash[String, String]) @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. # Whether the strategy can be applied to the provided URL.
# #
# @param url [String] the URL to match against # @param url [String] the URL to match against
+6
View File
@@ -54,6 +54,12 @@ class MacOSVersion < Version
new(str) new(str)
end 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 } sig { params(version: T.nilable(String)).void }
def initialize(version) def initialize(version)
raise MacOSVersion::Error, version unless /\A\d{2,}(?:\.\d+){0,2}\z/.match?(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 /system/bin/linker
].freeze ].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 # The path to the system's dynamic linker or `nil` if not found
sig { returns(T.nilable(::Pathname)) } sig { returns(T.nilable(::Pathname)) }
def self.system_ld_so def self.system_ld_so
+5
View File
@@ -10,6 +10,11 @@ module OS
SOVERSION = 6 SOVERSION = 6
SONAME = T.let("libstdc++.so.#{SOVERSION}".freeze, String) 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) } sig { returns(T::Boolean) }
def self.below_ci_version? def self.below_ci_version?
system_version < LINUX_LIBSTDCXX_CI_VERSION system_version < LINUX_LIBSTDCXX_CI_VERSION
+2 -2
View File
@@ -116,8 +116,6 @@ module Homebrew
nil nil
end end
private
sig { params(keg: Keg).void } sig { params(keg: Keg).void }
def backup(keg) def backup(keg)
keg.unlink keg.unlink
@@ -132,6 +130,8 @@ module Homebrew
end end
end end
private
sig { params(keg: Keg, keg_was_linked: T::Boolean, verbose: T::Boolean).void } sig { params(keg: Keg, keg_was_linked: T::Boolean, verbose: T::Boolean).void }
def restore_backup(keg, keg_was_linked, verbose:) def restore_backup(keg, keg_was_linked, verbose:)
path = backup_path(keg) path = backup_path(keg)
+3
View File
@@ -357,6 +357,9 @@ class Resource
sig { returns(Bottle) } sig { returns(Bottle) }
attr_reader :bottle attr_reader :bottle
sig { params(manifest_annotations: T.nilable(T::Hash[String, String])).void }
attr_writer :manifest_annotations
sig { params(bottle: Bottle).void } sig { params(bottle: Bottle).void }
def initialize(bottle) def initialize(bottle)
super("#{bottle.name}_bottle_manifest") super("#{bottle.name}_bottle_manifest")
+2
View File
@@ -14,6 +14,8 @@ require_relative "io_read"
require_relative "move_to_extend_os" require_relative "move_to_extend_os"
require_relative "negate_include" require_relative "negate_include"
require_relative "no_fileutils_rmrf" 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 "non_public_api_usage"
require_relative "presence" require_relative "presence"
require_relative "public_api_cookbook" 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:) SandboxPathFilter.new(path: filter_path, type:)
end end
private
sig { returns(SandboxProfile) } sig { returns(SandboxProfile) }
attr_reader :profile 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) } sig { returns(T::Boolean) }
attr_reader :failed attr_reader :failed
@@ -630,14 +638,6 @@ class Sandbox
sig { void } sig { void }
def apply_sandbox; end 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 } sig { void }
def record_sandbox_log; end 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) SystemCommand.run!("git", args:, chdir:, env: { "GIT_TERMINAL_PROMPT" => "0" }, print_stderr: true)
end end
private :git_command!
# Install this {Tap}. # Install this {Tap}.
# #
@@ -58,8 +58,8 @@ RSpec.describe Homebrew::API::CaskStruct do
) )
Homebrew::API::CaskStruct::PREDICATES.each do |predicate| Homebrew::API::CaskStruct::PREDICATES.each do |predicate|
expect(struct.send(:"#{predicate}?")).to be(false), expect(struct.public_send(:"#{predicate}?")).to be(false),
"expected #{predicate}? to default to false" "expected #{predicate}? to default to false"
end end
end end
@@ -76,8 +76,8 @@ RSpec.describe Homebrew::API::CaskStruct do
) )
Homebrew::API::CaskStruct::PREDICATES.each do |predicate| Homebrew::API::CaskStruct::PREDICATES.each do |predicate|
expect(struct.send(:"#{predicate}?")).to be(true), expect(struct.public_send(:"#{predicate}?")).to be(true),
"expected #{predicate}? to be true" "expected #{predicate}? to be true"
end end
end end
end end
@@ -232,7 +232,7 @@ RSpec.describe Homebrew::API::CaskStruct do
struct = described_class.deserialize(hash) struct = described_class.deserialize(hash)
Homebrew::API::CaskStruct::PREDICATES.each do |predicate| Homebrew::API::CaskStruct::PREDICATES.each do |predicate|
expect(struct.send(:"#{predicate}?")).to be false expect(struct.public_send(:"#{predicate}?")).to be false
end end
end end
@@ -255,7 +255,7 @@ RSpec.describe Homebrew::API::CaskStruct do
struct = described_class.deserialize(hash) struct = described_class.deserialize(hash)
Homebrew::API::CaskStruct::PREDICATES.each do |predicate| 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 end
end end
@@ -106,8 +106,8 @@ RSpec.describe Homebrew::API::FormulaStruct do
) )
Homebrew::API::FormulaStruct::PREDICATES.each do |predicate| Homebrew::API::FormulaStruct::PREDICATES.each do |predicate|
expect(struct.send(:"#{predicate}?")).to be(false), expect(struct.public_send(:"#{predicate}?")).to be(false),
"expected #{predicate}? to default to false" "expected #{predicate}? to default to false"
end end
end end
@@ -126,8 +126,8 @@ RSpec.describe Homebrew::API::FormulaStruct do
) )
Homebrew::API::FormulaStruct::PREDICATES.each do |predicate| Homebrew::API::FormulaStruct::PREDICATES.each do |predicate|
expect(struct.send(:"#{predicate}?")).to be(true), expect(struct.public_send(:"#{predicate}?")).to be(true),
"expected #{predicate}? to be true" "expected #{predicate}? to be true"
end end
end end
end end
+4 -3
View File
@@ -7,14 +7,15 @@ RSpec.describe Homebrew::Bump do
describe "::redacted_url" do describe "::redacted_url" do
it "masks env-token credentials embedded in a push URL" do it "masks env-token credentials embedded in a push URL" do
allow(GitHub::API).to receive(:credentials).and_return("ghp_secrettoken") allow(GitHub::API).to receive(:credentials).and_return("ghp_secrettoken")
expect(described_class.send(:redacted_url, expect(described_class.redacted_url(
"https://x-access-token:ghp_secrettoken@github.com/Homebrew/homebrew-core")) "https://x-access-token:ghp_secrettoken@github.com/Homebrew/homebrew-core",
))
.to eq("https://x-access-token:******@github.com/Homebrew/homebrew-core") .to eq("https://x-access-token:******@github.com/Homebrew/homebrew-core")
end end
it "leaves a credential-free URL unchanged" do it "leaves a credential-free URL unchanged" do
allow(GitHub::API).to receive(:credentials).and_return(nil) 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") .to eq("https://github.com/Homebrew/homebrew-core")
end end
end end
+8 -9
View File
@@ -189,20 +189,19 @@ RSpec.describe Homebrew::Bundle::Dsl do
end end
it ".sanitize_brew_name" do it ".sanitize_brew_name" do
expect(described_class.send(:sanitize_brew_name, "homebrew/homebrew/foo")).to eql("foo") expect(described_class.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.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.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("foo")).to eql("foo")
end end
it ".sanitize_tap_name" do it ".sanitize_tap_name" do
expect(described_class.send(:sanitize_tap_name, "homebrew/homebrew-foo")).to eql("homebrew/foo") expect(described_class.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/foo")).to eql("homebrew/foo")
end end
it ".sanitize_cask_name" do it ".sanitize_cask_name" do
expect(described_class.send(:sanitize_cask_name, expect(described_class.sanitize_cask_name("homebrew/cask-versions/adoptopenjdk8")).to eql("adoptopenjdk8")
"homebrew/cask-versions/adoptopenjdk8")).to eql("adoptopenjdk8") expect(described_class.sanitize_cask_name("adoptopenjdk8")).to eql("adoptopenjdk8")
expect(described_class.send(:sanitize_cask_name, "adoptopenjdk8")).to eql("adoptopenjdk8")
end end
end end
@@ -280,7 +280,7 @@ RSpec.describe Homebrew::Bundle::Installer do
output = IO.pipe do |reader, writer| output = IO.pipe do |reader, writer|
allow(writer).to receive(:tty?).and_return(true) 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 writer.close
reader.read reader.read
@@ -293,7 +293,7 @@ RSpec.describe Homebrew::Bundle::Installer do
output = IO.pipe do |reader, writer| output = IO.pipe do |reader, writer|
allow(writer).to receive(:tty?).and_return(false) 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 writer.close
reader.read reader.read
@@ -417,7 +417,7 @@ RSpec.describe Homebrew::Bundle::Installer do
dependency_map = Homebrew::Bundle::ParallelInstaller.new( dependency_map = Homebrew::Bundle::ParallelInstaller.new(
entries, entries,
jobs: 2, no_upgrade: false, verbose: false, force: false, quiet: true, 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"]) expect(dependency_map.fetch("beta")).to eq(Set["alpha"])
end end
@@ -440,7 +440,7 @@ RSpec.describe Homebrew::Bundle::Installer do
dependency_map = Homebrew::Bundle::ParallelInstaller.new( dependency_map = Homebrew::Bundle::ParallelInstaller.new(
entries, entries,
jobs: 2, no_upgrade: false, verbose: false, force: false, quiet: true, 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"]) expect(dependency_map.fetch("alpha")).to eq(Set["gh"])
end 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_BREW_SKIP").and_return("mysql")
allow(ENV).to receive(:[]).with("HOMEBREW_BUNDLE_TAP_SKIP").and_return("org/repo") allow(ENV).to receive(:[]).with("HOMEBREW_BUNDLE_TAP_SKIP").and_return("org/repo")
allow(Formatter).to receive(:warning) allow(Formatter).to receive(:warning)
skipper.instance_variable_set(:@skipped_entries, nil) skipper.skipped_entries = nil
skipper.instance_variable_set(:@failed_taps, nil) skipper.failed_taps = nil
end end
describe ".skip?" do describe ".skip?" do
+1 -1
View File
@@ -99,7 +99,7 @@ RSpec.describe CacheStoreDatabase do
context "without an open database" do context "without an open database" do
before do before do
sample_db.instance_variable_set(:@db, nil) sample_db.db = nil
end end
it "does not raise an error when `close` is called on the database" do 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 it "skips relative paths" do
expect 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.to output(%r{Skipping delete for relative path 'relative/path'\.}).to_stderr
end end
@@ -36,7 +36,7 @@ RSpec.describe Cask::Artifact::AbstractUninstall, :cask do
tmpdir/"nested/./#{valid_path.basename}", tmpdir/"nested/./#{valid_path.basename}",
].each do |invalid_path| ].each do |invalid_path|
expect do 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( end.to output(
/Skipping delete for path with relative segments '#{Regexp.escape(invalid_path.to_s)}'\./, /Skipping delete for path with relative segments '#{Regexp.escape(invalid_path.to_s)}'\./,
).to_stderr ).to_stderr
@@ -49,7 +49,7 @@ RSpec.describe Cask::Artifact::AbstractUninstall, :cask do
invalid_path = "~/../each_resolved_path_#{artifact_dsl_key}" invalid_path = "~/../each_resolved_path_#{artifact_dsl_key}"
expect do 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( end.to output(
/Skipping delete for path with relative segments '#{Regexp.escape(invalid_path)}'\./, /Skipping delete for path with relative segments '#{Regexp.escape(invalid_path)}'\./,
).to_stderr ).to_stderr
@@ -66,7 +66,7 @@ RSpec.describe Cask::Artifact::AbstractUninstall, :cask do
allow(artifact).to receive(:undeletable?) { |target| target == undeletable_path } allow(artifact).to receive(:undeletable?) { |target| target == undeletable_path }
expect do 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]]]) .to eq([["#{glob_dir}/*.plist", [safe_path]]])
end.to output( end.to output(
/Skipping delete for undeletable path '#{Regexp.escape(undeletable_path.to_s)}'\./, /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)) allow(MacOS).to receive(:version).and_return(MacOSVersion.from_symbol(:ventura))
expect do 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) end.to raise_error(SystemExit)
.and output(/Full Disk Access/).to_stderr .and output(/Full Disk Access/).to_stderr
end end
@@ -315,7 +315,7 @@ RSpec.describe Cask::Artifact::App, :cask do
allow(MacOS).to receive(:version).and_return(MacOSVersion.from_symbol(:sonoma)) 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 end
it "uses portable copy arguments on older macOS versions", :needs_macos do 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)) 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 end
it "uses portable copy arguments across filesystems", :needs_macos do 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_path).to receive(:dirname).and_return(source_dir)
allow(source_dir).to receive(:stat).and_return(instance_double(File::Stat, dev: 2)) 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
end end
@@ -5,13 +5,11 @@ RSpec.describe Cask::Artifact::GeneratedCompletion, :cask do
let(:staged_path) { Pathname(Dir.mktmpdir) } let(:staged_path) { Pathname(Dir.mktmpdir) }
let(:cask) do let(:cask) do
tmp_staged = staged_path
Cask::Cask.new("test-generated-completion") do Cask::Cask.new("test-generated-completion") do
version "1.0" version "1.0"
sha256 :no_check sha256 :no_check
url "file:///dev/null" url "file:///dev/null"
generate_completions_from_executable "bin/foo", "completions" generate_completions_from_executable "bin/foo", "completions"
instance_variable_set(:@staged_path, tmp_staged)
end end
end end
@@ -20,6 +18,7 @@ RSpec.describe Cask::Artifact::GeneratedCompletion, :cask do
let(:fish_dir) { cask.config.fish_completion } let(:fish_dir) { cask.config.fish_completion }
before do before do
allow(cask).to receive(:staged_path).and_return(staged_path)
(staged_path/"bin").mkpath (staged_path/"bin").mkpath
(staged_path/"bin/foo").write("#!/bin/sh\necho \"$SHELL completion\"") (staged_path/"bin/foo").write("#!/bin/sh\necho \"$SHELL completion\"")
(staged_path/"bin/foo").chmod(0755) (staged_path/"bin/foo").chmod(0755)
@@ -134,14 +133,12 @@ RSpec.describe Cask::Artifact::GeneratedCompletion, :cask do
context "with specific shells and format" do context "with specific shells and format" do
let(:cask) do let(:cask) do
tmp_staged = staged_path
Cask::Cask.new("test-generated-completion") do Cask::Cask.new("test-generated-completion") do
version "1.0" version "1.0"
sha256 :no_check sha256 :no_check
url "file:///dev/null" url "file:///dev/null"
generate_completions_from_executable "bin/foo", "completions", generate_completions_from_executable "bin/foo", "completions",
shells: [:zsh], shell_parameter_format: :arg, base_name: "bar" shells: [:zsh], shell_parameter_format: :arg, base_name: "bar"
instance_variable_set(:@staged_path, tmp_staged)
end end
end end
@@ -175,14 +172,12 @@ RSpec.describe Cask::Artifact::GeneratedCompletion, :cask do
context "with string shells" do context "with string shells" do
let(:cask) do let(:cask) do
tmp_staged = staged_path
Cask::Cask.new("test-generated-completion") do Cask::Cask.new("test-generated-completion") do
version "1.0" version "1.0"
sha256 :no_check sha256 :no_check
url "file:///dev/null" url "file:///dev/null"
generate_completions_from_executable "bin/foo", "completions", generate_completions_from_executable "bin/foo", "completions",
shells: %w[bash zsh fish pwsh] shells: %w[bash zsh fish pwsh]
instance_variable_set(:@staged_path, tmp_staged)
end end
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)
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
end end
@@ -46,7 +46,7 @@ RSpec.describe Cask::Artifact::Relocated, :cask do
print_stderr: false) print_stderr: false)
expect(command).to receive(:run!).twice 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 end
end end
@@ -186,9 +186,8 @@ RSpec.shared_examples "#uninstall_phase or #zap_phase" do
.with("/bin/launchctl", args: ["list"]) .with("/bin/launchctl", args: ["list"])
.and_return(instance_double(SystemCommand::Result, stdout: launchctl_list)) .and_return(instance_double(SystemCommand::Result, stdout: launchctl_list))
expect(subject.send(:find_launchctl_with_wildcard, expect(subject.find_launchctl_with_wildcard("my.fancy.package.service.*"))
"my.fancy.package.service.*")).to eq(["my.fancy.package.service.12345", .to eq(["my.fancy.package.service.12345", "my.fancy.package.service.test"])
"my.fancy.package.service.test"])
end end
end end
@@ -149,7 +149,7 @@ RSpec.describe Cask::Artifact::Uninstall, :cask do
allow(artifact).to receive(:quit).with(bundle_id) allow(artifact).to receive(:quit).with(bundle_id)
.and_return(instance_double(SystemCommand::Result, success?: true)) .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] expect(artifact.bundle_ids_to_reopen).to eq [bundle_id]
end end
@@ -159,7 +159,7 @@ RSpec.describe Cask::Artifact::Uninstall, :cask do
allow(artifact).to receive(:quit).with(bundle_id) allow(artifact).to receive(:quit).with(bundle_id)
.and_return(instance_double(SystemCommand::Result, success?: true)) .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 expect(artifact.bundle_ids_to_reopen).to be_empty
end end
@@ -171,7 +171,7 @@ RSpec.describe Cask::Artifact::Uninstall, :cask do
allow(Timeout).to receive(:timeout).and_raise(Timeout::Error) allow(Timeout).to receive(:timeout).and_raise(Timeout::Error)
expect do 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 end.to output(/did not quit/).to_stderr
expect(artifact.bundle_ids_to_reopen).to be_empty 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) allow(Cask::Quarantine).to receive(:available?).and_return(false)
expect(Cask::Quarantine).not_to receive(:detect) expect(Cask::Quarantine).not_to receive(:detect)
audit.send(:extract_artifacts) audit.extract_artifacts
end end
end end
@@ -555,9 +555,9 @@ RSpec.describe Cask::Audit, :cask do
let(:cask_token) { "basic-cask" } let(:cask_token) { "basic-cask" }
it "returns existing `@livecheck_result` value" do 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) expect(run).not_to error_with(message)
audit.instance_variable_set(:@livecheck_result, nil) audit.livecheck_result = nil
end end
end end
@@ -1237,7 +1237,7 @@ RSpec.describe Cask::Audit, :cask do
end end
it "normalizes 10.16.0 minimum macOS to Big Sur" do 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
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 it "returns true if @any_named_args is true" do
auditor_obj = auditor.new(cask, any_named_args: true) 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 end
it "returns true if @audit_strict is true" do it "returns true if @audit_strict is true" do
auditor_obj = auditor.new(cask, audit_strict: true) 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 end
it "returns false if the audit argument is nil" do it "returns false if the audit argument is nil" do
auditor_obj = auditor.new(cask) auditor_obj = auditor.new(cask)
expect(auditor_obj.send(:output_summary?)).to be(false) expect(auditor_obj.output_summary?).to be(false)
expect(auditor_obj.send(:output_summary?, nil)).to be(false) expect(auditor_obj.output_summary?(nil)).to be(false)
end end
it "returns false if there are no audit errors" do it "returns false if there are no audit errors" do
auditor_obj = auditor.new(cask) auditor_obj = auditor.new(cask)
audit = Cask::Audit.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 end
it "returns true if there are audit errors" do it "returns true if there are audit errors" do
auditor_obj = auditor.new(cask) auditor_obj = auditor.new(cask)
audit = Cask::Audit.new(cask) audit = Cask::Audit.new(cask)
audit.instance_variable_set(:@errors, [{ audit.add_error(nil)
message: nil, expect(auditor_obj.output_summary?(audit)).to be(true)
location: nil,
corrected: false,
}])
expect(auditor_obj.send(:output_summary?, audit)).to be(true)
end end
end end
end end
@@ -425,7 +425,7 @@ RSpec.describe Cask::CaskLoader, :cask do
it "raises CaskUnreadableError when loaded from installed caskfile" do it "raises CaskUnreadableError when loaded from installed caskfile" do
loader = Cask::CaskLoader::FromPathLoader.new(cask_file) 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/) expect { loader.load(config: nil) }.to raise_error(Cask::CaskUnreadableError, /appcast/)
end end
end end
@@ -491,7 +491,7 @@ RSpec.describe Cask::CaskLoader, :cask do
it "raises CaskUnreadableError when loaded from installed caskfile" do it "raises CaskUnreadableError when loaded from installed caskfile" do
loader = Cask::CaskLoader::FromPathLoader.new(cask_file) 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/) expect { loader.load(config: nil) }.to raise_error(Cask::CaskUnreadableError, /Unknown key: :formula/)
end end
end end
+1 -1
View File
@@ -4,7 +4,7 @@
require "cask/caskroom" require "cask/caskroom"
RSpec.describe Cask::Caskroom do 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 describe ".ensure_caskroom_exists" do
it "changes the group when sudo is unnecessary and the group is wrong" 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 RSpec.describe Cask::Download, :cask do
describe "#download_name" 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(:token) { "example-cask" }
let(:full_token) { token } let(:full_token) { token }
@@ -73,7 +73,7 @@ RSpec.describe Cask::DSL::Version, :cask do
end end
shared_examples "version expectations hash" do |method, hash| shared_examples "version expectations hash" do |method, hash|
subject { version.send(method) } subject { version.public_send(method) }
include_examples "expectations hash", :raw_version, include_examples "expectations hash", :raw_version,
{ :latest => "latest", { :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 it "allows installer manual to be specified" do
installer = cask.artifacts.first 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")) expect(installer.path).to eq(Pathname("Caffeine.app"))
end end
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) allow(cask).to receive(:staged_path).and_return(staged_path)
installer = described_class.new(cask) 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 / "Renamed App.app").to be_a_directory
expect(staged_path / "Original App.app").not_to exist 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) allow(cask).to receive(:staged_path).and_return(staged_path)
installer = described_class.new(cask) 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 / "Final Name.app").to be_a_directory
expect(staged_path / "Original.app").not_to exist 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) allow(cask).to receive(:staged_path).and_return(staged_path)
installer = described_class.new(cask) 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").to be_a_file
expect((staged_path / "Test App.pkg").read).to eq("test content") 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) 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 / "Different.app").to be_a_directory
expect(staged_path / "Target.app").not_to exist expect(staged_path / "Target.app").not_to exist
end end
@@ -102,7 +102,7 @@ RSpec.describe Homebrew::Cmd::Bundle::CheckSubcommand, :no_api do
end end
it "raises an error for an implicitly unlinked non-keg-only formula" do 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_any_instance_of(Pathname).to receive(:read).and_return("brew 'abc'")
allow(Formula["abc"]).to receive(:linked?).and_return(false) allow(Formula["abc"]).to receive(:linked?).and_return(false)
@@ -111,7 +111,7 @@ RSpec.describe Homebrew::Cmd::Bundle::CheckSubcommand, :no_api do
end end
it "does not raise an error when live link status satisfies an implicit check" do 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_any_instance_of(Pathname).to receive(:read).and_return("brew 'abc'")
allow(Formula["abc"]).to receive(:linked?).and_return(true) allow(Formula["abc"]).to receive(:linked?).and_return(true)
@@ -130,7 +130,7 @@ RSpec.describe Homebrew::Cmd::Bundle::CheckSubcommand, :no_api do
end end
it "outputs the implicit link status error" do 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_any_instance_of(Pathname).to receive(:read).and_return("brew 'abc'")
allow(Formula["abc"]).to receive(:linked?).and_return(true) 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 describe "#cask_downloads", :cask do
it "collects one download per distinct URL across all platforms" do it "collects one download per distinct URL across all platforms" do
cmd = described_class.new(["--cask", "--all-platforms", "sha256-os"]) 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) } .map { |download| File.basename(download.url.to_s) }
expect(basenames).to contain_exactly("caffeine-arm-darwin.zip", "caffeine-intel-darwin.zip", expect(basenames).to contain_exactly("caffeine-arm-darwin.zip", "caffeine-intel-darwin.zip",
"caffeine-arm-linux.zip", "caffeine-intel-linux.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 it "skips arches the cask's depends_on arch excludes" do
cmd = described_class.new(["--cask", "--os=macos", "--arch=intel", "depends-on-arch-arm64"]) 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 end
it "collapses to a single download for a cask without on_system blocks" do it "collapses to a single download for a cask without on_system blocks" do
cmd = described_class.new(["--cask", "--all-platforms", "local-caffeine"]) 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 end
end end
+51 -51
View File
@@ -256,7 +256,7 @@ RSpec.describe Homebrew::Cmd::Info do
Formula from https://example.com/testball.rb Formula from https://example.com/testball.rb
Not installed Not installed
EOS EOS
expect { info.send(:info_formula_summary, formula) } expect { info.info_formula_summary(formula) }
.to output(expected_output).to_stdout .to output(expected_output).to_stdout
.and not_to_output.to_stderr .and not_to_output.to_stderr
end 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(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]) 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 .to output(/Installs from source: yes/).to_stdout
.and not_to_output(/Metadata/).to_stdout .and not_to_output(/Metadata/).to_stdout
.and not_to_output(/supports macOS and Linux/).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(other).to receive(:full_name).and_return("someuser/tap/other")
allow(Formulary).to receive(:factory).with("other").and_return(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 .to output(%r{Conflicts with:\n someuser/tap/other}).to_stdout
end 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(info).to receive(:github_info).with(formula).and_return("https://example.com/testball.rb")
allow(Formulary).to receive(:factory).with("testball").and_return(formula) 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 .not_to output(/Conflicts with:/).to_stdout
end 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(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]) 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 .to output(/==> .*testball.*\(deprecated\):/).to_stdout
.and not_to_output.to_stderr .and not_to_output.to_stderr
end 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(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]) 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 .to output(/==> .*testball.*\(disabled\):/).to_stdout
.and not_to_output.to_stderr .and not_to_output.to_stderr
end end
@@ -379,7 +379,7 @@ RSpec.describe Homebrew::Cmd::Info do
to_formulae_and_casks_and_unavailable: [core, core], 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 .to output(%r{ataraxy-labs/tap/testball.*homebrew/core/testball.*Not installed}m).to_stdout
end end
@@ -391,7 +391,7 @@ RSpec.describe Homebrew::Cmd::Info do
to_formulae_and_casks_and_unavailable: [error], 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 .to output(/No available formula or cask with the name "nonexistent-formula"/).to_stderr
end 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(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) 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 .to output(%r{homebrew/core/testball.*Not installed.*ataraxy-labs/tap/testball}m).to_stdout
end end
@@ -431,7 +431,7 @@ RSpec.describe Homebrew::Cmd::Info do
end end
allow(Formulary).to receive(:factory).with("ataraxy-labs/tap/testball").and_return(keg_formula) 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 end
it "resolves the keg's own name when it differs from the formula (installed via alias)" do 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" } 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) 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 end
it "returns the original formula and no shadowing tap when the install receipt has no tap" do 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.tabfile = keg_path/AbstractTab::FILENAME
tab.write tab.write
expect(info.send(:installed_resolution, formula)).to eq([formula, nil]) expect(info.installed_resolution(formula)).to eq([formula, nil])
end end
it "returns the original formula and no shadowing tap when the install receipt's tap matches" do 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 tab.write
allow(formula).to receive(:tap).and_return(Tap.fetch("homebrew/core")) 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 end
it "warns about a shadowing tap when info_formula is given one" do 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(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]) 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 .to output(%r{Warning: `testball` shadows `homebrew/core/testball`}).to_stdout
.and not_to_output.to_stderr .and not_to_output.to_stderr
end end
@@ -495,7 +495,7 @@ RSpec.describe Homebrew::Cmd::Info do
allow(formula).to receive(:tap).and_return(Tap.fetch("homebrew/core")) allow(formula).to receive(:tap).and_return(Tap.fetch("homebrew/core"))
qualified = Set["homebrew/core/testball"] 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 end
it "treats a bare unqualified input as not user-qualified" do 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" url "https://brew.sh/testball-0.1.tar.gz"
end 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 end
it "--json swaps an unqualified-input formula to its installed tap" do 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" \ "==> Dependencies\nRequired \\(1\\): .*bar.*\n" \
"Recursive Runtime \\(2\\): 1 installed .*✔, 1 missing .*✘\nDependents: 1", "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 .to output(expected_output).to_stdout
.and not_to_output(/^Dependencies: /).to_stdout .and not_to_output(/^Dependencies: /).to_stdout
.and not_to_output.to_stderr .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(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]) 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 .to output(/^Dependents \(2\): another-dependent, some-dependent$/).to_stdout
.and not_to_output(/^Dependents: /).to_stdout .and not_to_output(/^Dependents: /).to_stdout
.and not_to_output.to_stderr .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(formula).to receive_messages(core_formula?: false, missing_library_linkage: [[], Set.new])
allow(direct_dependency).to receive(:satisfied?).and_return(true) 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 .to output(/Recursive Runtime \(1\): all installed .*✔/).to_stdout
.and not_to_output(/missing/).to_stdout .and not_to_output(/missing/).to_stdout
.and not_to_output.to_stderr .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(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]) 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 .to output(/Required \(1\): .*bar.*✘/).to_stdout
.and not_to_output.to_stderr .and not_to_output.to_stderr
end 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(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]) 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 .to output(/Required \(1\): .*bar.*✔/).to_stdout
.and not_to_output.to_stderr .and not_to_output.to_stderr
end 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(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]) 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 .to output(/Required \(1\): .*bar.*↑/).to_stdout
.and not_to_output.to_stderr .and not_to_output.to_stderr
end 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(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]) 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 .to output(/Required \(1\): .*bar.*✔/).to_stdout
.and not_to_output.to_stderr .and not_to_output.to_stderr
end 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(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]) 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 .to output(/Required \(1\): .*bar.*↑/).to_stdout
.and not_to_output.to_stderr .and not_to_output.to_stderr
end 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(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]) 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 .to output(/Required \(1\): .*pkg-config.*✔/).to_stdout
.and not_to_output.to_stderr .and not_to_output.to_stderr
end 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(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]) 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 .to output(/Required \(1\): bar\n/).to_stdout
.and not_to_output.to_stderr .and not_to_output.to_stderr
end 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(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]) 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 .to output(/Required \(1\): .*bar.*✘/).to_stdout
.and not_to_output.to_stderr .and not_to_output.to_stderr
end 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(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]) 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 .to output(/Required \(1\): .*bar.*↑/).to_stdout
.and not_to_output.to_stderr .and not_to_output.to_stderr
end end
@@ -981,7 +981,7 @@ RSpec.describe Homebrew::Cmd::Info do
pour_bottle?: true, pour_bottle?: true,
) )
expect { info.send(:info_formula, formula) } expect { info.info_formula(formula) }
.to not_to_output(/Build \(1\): .*bar.*/).to_stdout .to not_to_output(/Build \(1\): .*bar.*/).to_stdout
.and not_to_output(/==> Dependencies/).to_stdout .and not_to_output(/==> Dependencies/).to_stdout
.and not_to_output.to_stderr .and not_to_output.to_stderr
@@ -1004,7 +1004,7 @@ RSpec.describe Homebrew::Cmd::Info do
tab.tabfile = keg_path/AbstractTab::FILENAME tab.tabfile = keg_path/AbstractTab::FILENAME
tab.write 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 .to output(/\A==> testball: 0\.0\.1 → stable 0\.1\n/).to_stdout
.and not_to_output.to_stderr .and not_to_output.to_stderr
end end
@@ -1025,7 +1025,7 @@ RSpec.describe Homebrew::Cmd::Info do
requirements: Requirements.new(LinuxRequirement.new), requirements: Requirements.new(LinuxRequirement.new),
) )
expect { info.send(:info_formula, formula) } expect { info.info_formula(formula) }
.to output(/Requirements\nRequired: .*Linux/).to_stdout .to output(/Requirements\nRequired: .*Linux/).to_stdout
.and not_to_output(/supports Linux/).to_stdout .and not_to_output(/supports Linux/).to_stdout
.and not_to_output.to_stderr .and not_to_output.to_stderr
@@ -1048,7 +1048,7 @@ RSpec.describe Homebrew::Cmd::Info do
requirements: Requirements.new(os_requirement), 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 .to not_to_output(/Installs from source: yes/).to_stdout
.and not_to_output.to_stderr .and not_to_output.to_stderr
end 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(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]) 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 .to output(a_string_including("==> Binaries\nanother\ndaemon\ntestball\n")).to_stdout
.and not_to_output.to_stderr .and not_to_output.to_stderr
end end
@@ -1101,7 +1101,7 @@ RSpec.describe Homebrew::Cmd::Info do
allow(formula).to receive_messages(bottle:, core_formula?: false) allow(formula).to receive_messages(bottle:, core_formula?: false)
allow(info).to receive(:github_info).with(formula).and_return("https://example.com/testball.rb") 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 .to output(a_string_including("==> Binaries\nanother\ndaemon\ntestball\n")).to_stdout
.and not_to_output.to_stderr .and not_to_output.to_stderr
end 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(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]) 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 .to not_to_output(/==> Binaries/).to_stdout
.and not_to_output.to_stderr .and not_to_output.to_stderr
end 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(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]) 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 .to not_to_output(/==> Binaries/).to_stdout
.and not_to_output.to_stderr .and not_to_output.to_stderr
end 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(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") 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 .to output(/^Aliases: testball@1\.0, tball, googleball$/).to_stdout
.and not_to_output(/^Old Names:/).to_stdout .and not_to_output(/^Old Names:/).to_stdout
.and not_to_output.to_stderr .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(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") 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 .to output(/^Aliases: testball@1\.0, tball\nOld Names: foo, bar$/).to_stdout
.and not_to_output.to_stderr .and not_to_output.to_stderr
end end
@@ -1268,7 +1268,7 @@ RSpec.describe Homebrew::Cmd::Info do
allow(main_formula).to receive_messages(aliases: [], oldnames: ["foo"]) 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") 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 .to output(/^Old Names: foo$/).to_stdout
.and not_to_output(/^Aliases:/).to_stdout .and not_to_output(/^Aliases:/).to_stdout
.and not_to_output.to_stderr .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(main_formula).to receive_messages(aliases: [], oldnames: [])
allow(info).to receive(:github_info).with(main_formula).and_return("https://example.com/testball.rb") 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 .to not_to_output(/^Aliases:/).to_stdout
.and not_to_output(/^Old Names:/).to_stdout .and not_to_output(/^Old Names:/).to_stdout
.and not_to_output.to_stderr .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(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") 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( .to output(Regexp.new(
"==> Installed Versions\n" \ "==> Installed Versions\n" \
".*testball\\b.*\\s+1\\.0\\s+\\(.*\\)\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(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") 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 .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(/0\.9 →/).to_stdout
.and not_to_output.to_stderr .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(main_formula).to receive(:versioned_formulae).and_return([])
allow(info).to receive(:github_info).with(main_formula).and_return("https://example.com/testball.rb") 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 .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(/\s+0\.9\s+\(/).to_stdout
.and not_to_output.to_stderr .and not_to_output.to_stderr
@@ -1406,7 +1406,7 @@ RSpec.describe Homebrew::Cmd::Info do
linked_version: PkgVersion.parse("1.0")) linked_version: PkgVersion.parse("1.0"))
allow(info).to receive(:github_info).with(main_formula).and_return("https://example.com/testball.rb") 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 .to output(/.*testball\b.*\s+1\.0\s+\(.*\)\s+\[Linked\]/).to_stdout
.and not_to_output.to_stderr .and not_to_output.to_stderr
end end
@@ -1439,7 +1439,7 @@ RSpec.describe Homebrew::Cmd::Info do
allow(Formulary).to receive(:factory).with("testball").and_return(parent) 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") 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( .to output(Regexp.new(
"==> Installed Versions\n" \ "==> Installed Versions\n" \
".*testball\\b.*\\s+1\\.0\\s+\\(.*\\)\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(main_formula).to receive(:versioned_formulae).and_return([])
allow(info).to receive(:github_info).with(main_formula).and_return("https://example.com/testball.rb") 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 .to output(/==> Installed Versions\n.*testball\b.*\s+1\.0\s+\(/).to_stdout
.and not_to_output.to_stderr .and not_to_output.to_stderr
end end
@@ -1491,7 +1491,7 @@ RSpec.describe Homebrew::Cmd::Info do
allow(Formulary).to receive(:factory).with("testball").and_return(parent) 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") 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 .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(/testball@0\.9 \(0\.9\)/).to_stdout
.and not_to_output.to_stderr .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(main_formula).to receive(:versioned_formulae).and_return([])
allow(info).to receive(:github_info).with(main_formula).and_return("https://example.com/testball.rb") 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( .to output(Regexp.new(
"==> Installed Kegs and Versions\n" \ "==> Installed Kegs and Versions\n" \
".*testball\\b.*\\s+1\\.0\\b.*\\(.*\\)\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(main_formula).to receive(:versioned_formulae).and_return([])
allow(info).to receive(:github_info).with(main_formula).and_return("https://example.com/testball.rb") 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 .to not_to_output(/==> Installed Versions\b/).to_stdout
.and not_to_output.to_stderr .and not_to_output.to_stderr
end end
@@ -1554,7 +1554,7 @@ RSpec.describe Homebrew::Cmd::Info do
url "https://brew.sh/testball-0.1.tar.gz" url "https://brew.sh/testball-0.1.tar.gz"
end 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) .to eq(keg_formula_path.to_s)
end end
@@ -1565,7 +1565,7 @@ RSpec.describe Homebrew::Cmd::Info do
url "https://brew.sh/testball-0.1.tar.gz" url "https://brew.sh/testball-0.1.tar.gz"
end 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/" \ .to eq("https://github.com/Homebrew/homebrew-core/blob/HEAD/" \
"#{formula_path.relative_path_from(tap.path)}") "#{formula_path.relative_path_from(tap.path)}")
end end
+2 -2
View File
@@ -48,7 +48,7 @@ RSpec.describe Homebrew::Cmd::Outdated do
expect(cask).to receive(:outdated?) expect(cask).to receive(:outdated?)
.with(greedy: false, greedy_latest: false, greedy_auto_updates: false) .with(greedy: false, greedy_latest: false, greedy_auto_updates: false)
.and_return(false) .and_return(false)
expect(cmd.send(:select_outdated, [cask])).to be_empty expect(cmd.select_outdated([cask])).to be_empty
end end
it "checks auto-updating casks with --greedy-auto-updates", :cask do 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?) expect(cask).to receive(:outdated?)
.with(greedy: false, greedy_latest: false, greedy_auto_updates: true) .with(greedy: false, greedy_latest: false, greedy_auto_updates: true)
.and_return(true) .and_return(true)
expect(cmd.send(:select_outdated, [cask])).to eq([cask]) expect(cmd.select_outdated([cask])).to eq([cask])
end end
it "excludes auto-updating casks when auto-update upgrades are disabled", :cask do 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) } before { allow_any_instance_of(StringIO).to receive(:tty?).and_return(false) }
it "skips" do 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 .not_to output.to_stdout
end end
end end
@@ -38,25 +38,25 @@ RSpec.describe Homebrew::Cmd::SearchCmd do
before { allow_any_instance_of(StringIO).to receive(:tty?).and_return(true) } before { allow_any_instance_of(StringIO).to receive(:tty?).and_return(true) }
it "skips a regex query" do 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 .not_to output.to_stdout
end end
it "skips if there is not a reason" do it "skips if there is not a reason" do
allow(Homebrew::MissingFormula).to receive(:reason).and_return(nil) 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 .not_to output.to_stdout
end end
it "prints additional output if `found_matches` is true" do it "prints additional output if `found_matches` is true" do
allow(Homebrew::MissingFormula).to receive(:reason).and_return("Reason") 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 .to output("\nIf you meant \"formula\" specifically:\nReason\n").to_stdout
end end
it "only prints reason if `found_matches` is false" do it "only prints reason if `found_matches` is false" do
allow(Homebrew::MissingFormula).to receive(:reason).and_return("Reason") 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 .to output("Reason\n").to_stdout
end end
end end
@@ -17,7 +17,7 @@ RSpec.shared_examples "parseable arguments" do |command_name: nil|
klass = Object.const_get(command) klass = Object.const_get(command)
# rubocop:enable Sorbet/ConstantsFromStrings # rubocop:enable Sorbet/ConstantsFromStrings
end 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) cmd = klass.new(argv)
expect(cmd.args).to be_a Homebrew::CLI::Args expect(cmd.args).to be_a Homebrew::CLI::Args
end end
+28 -26
View File
@@ -16,85 +16,85 @@ RSpec.describe Homebrew::Cmd::Source do
describe "#github_repo_url" do describe "#github_repo_url" do
it "extracts repository URL from GitHub 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") .to eq("https://github.com/Homebrew/brew")
end end
it "handles GitHub archive URLs" do 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") .to eq("https://github.com/Homebrew/testball")
end end
it "returns nil for non-GitHub URLs" do 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 .to be_nil
end end
end end
describe "#gitlab_repo_url" do describe "#gitlab_repo_url" do
it "extracts repository URL from GitLab URL with nested groups" 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") .to eq("https://gitlab.com/group/subgroup/project")
end end
it "handles GitLab .git URLs" do 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") .to eq("https://gitlab.com/user/repo")
end end
it "returns nil for non-GitLab URLs" do 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 .to be_nil
end end
end end
describe "#bitbucket_repo_url" do describe "#bitbucket_repo_url" do
it "extracts repository URL from Bitbucket 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") .to eq("https://bitbucket.org/user/repo")
end end
it "handles Bitbucket .git URLs" do 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") .to eq("https://bitbucket.org/user/repo")
end end
it "returns nil for non-Bitbucket URLs" do 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 .to be_nil
end end
end end
describe "#codeberg_repo_url" do describe "#codeberg_repo_url" do
it "extracts repository URL from Codeberg 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") .to eq("https://codeberg.org/user/repo")
end end
it "handles Codeberg .git URLs" do 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") .to eq("https://codeberg.org/user/repo")
end end
it "returns nil for non-Codeberg URLs" do 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 .to be_nil
end end
end end
describe "#sourcehut_repo_url" do describe "#sourcehut_repo_url" do
it "extracts repository URL from SourceHut 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") .to eq("https://sr.ht/~user/repo")
end end
it "handles sr.ht URLs without git subdomain" do 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") .to eq("https://sr.ht/~user/repo")
end end
it "returns nil for non-SourceHut URLs" do 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 .to be_nil
end end
end end
@@ -118,8 +118,9 @@ RSpec.describe Homebrew::Cmd::Source do
]) ])
expect(described_class.new([]) expect(described_class.new([])
.send(:pypi_repo_url, .pypi_repo_url(
"https://files.pythonhosted.org/packages/24/62/ae72ff66c0f1fd959925b4c11f8c2dea61f47f6acaea75a08512cdfe3fed/numpy-2.4.1.tar.gz")) "https://files.pythonhosted.org/packages/24/62/ae72ff66c0f1fd959925b4c11f8c2dea61f47f6acaea75a08512cdfe3fed/numpy-2.4.1.tar.gz",
))
.to eq("https://github.com/numpy/numpy") .to eq("https://github.com/numpy/numpy")
end end
@@ -139,13 +140,14 @@ RSpec.describe Homebrew::Cmd::Source do
]) ])
expect(described_class.new([]) expect(described_class.new([])
.send(:pypi_repo_url, .pypi_repo_url(
"https://files.pythonhosted.org/packages/00/00/000000000000000000000000000000000000000000000000000000000000/foobar-0.0.1.tar.gz")) "https://files.pythonhosted.org/packages/00/00/000000000000000000000000000000000000000000000000000000000000/foobar-0.0.1.tar.gz",
))
.to be_nil .to be_nil
end end
it "returns nil for non-PyPI URLs" do 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 .to be_nil
end end
end end
@@ -168,7 +170,7 @@ RSpec.describe Homebrew::Cmd::Source do
instance_double(Process::Status, success?: true), 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") .to eq("https://github.com/vitejs/vite.git")
end end
end end
@@ -183,29 +185,29 @@ RSpec.describe Homebrew::Cmd::Source do
]) ])
expect(described_class.new([]) 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 .to be_nil
end end
it "returns nil for non-npm URLs" do 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 .to be_nil
end end
end end
describe "#url_to_repo" do describe "#url_to_repo" do
it "returns GitHub repo URL for GitHub URLs" 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") .to eq("https://github.com/Homebrew/brew")
end end
it "returns GitLab repo URL for GitLab URLs" do 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") .to eq("https://gitlab.com/user/repo")
end end
it "returns nil for unsupported URLs" do 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 .to be_nil
end end
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) allow(Homebrew::Trust).to receive(:trusted_tap?).with(tap).and_return(true)
with_env(HOMEBREW_REQUIRE_TAP_TRUST: "1") do 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 .to output(%r{thirdparty/foo: Installed\nTrusted\nNo commands/casks/formulae}).to_stdout
end end
end end
@@ -76,7 +76,7 @@ RSpec.describe Homebrew::Cmd::TapInfo do
allow(Homebrew::Trust).to receive(:trusted_tap?).with(tap).and_return(false) allow(Homebrew::Trust).to receive(:trusted_tap?).with(tap).and_return(false)
with_env(HOMEBREW_REQUIRE_TAP_TRUST: "1") do 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 .to output(%r{thirdparty/foo: Installed\nUntrusted\nNo commands/casks/formulae}).to_stdout
end end
end end
@@ -88,7 +88,7 @@ RSpec.describe Homebrew::Cmd::TapInfo do
instance_double(Tap, to_hash: { "name" => "user/b" }), 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 .to output(%r{"name":\s*"user/a".*"name":\s*"user/b"}m).to_stdout
end end
end end
@@ -103,35 +103,35 @@ RSpec.describe Homebrew::Cmd::TapInfo do
end end
it "does not mark an uninstalled formula" do 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 end
it "marks an installed formula as satisfied" do it "marks an installed formula as satisfied" do
formula = instance_double(Formula, outdated?: false, deprecated?: false, disabled?: false) formula = instance_double(Formula, outdated?: false, deprecated?: false, disabled?: false)
allow(Formulary).to receive(:factory).with("homebrew/foo/installed").and_return(formula) 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 end
it "marks an outdated installed formula as upgradable" do it "marks an outdated installed formula as upgradable" do
formula = instance_double(Formula, outdated?: true, deprecated?: false, disabled?: false) formula = instance_double(Formula, outdated?: true, deprecated?: false, disabled?: false)
allow(Formulary).to receive(:factory).with("homebrew/foo/outdated").and_return(formula) 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 end
it "marks a deprecated formula with `(deprecated)`" do it "marks a deprecated formula with `(deprecated)`" do
formula = instance_double(Formula, outdated?: false, deprecated?: true, disabled?: false) formula = instance_double(Formula, outdated?: false, deprecated?: true, disabled?: false)
allow(Formulary).to receive(:factory).with("homebrew/foo/old").and_return(formula) 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 end
it "marks a disabled formula with `(disabled)`" do it "marks a disabled formula with `(disabled)`" do
formula = instance_double(Formula, outdated?: false, deprecated?: false, disabled?: true) formula = instance_double(Formula, outdated?: false, deprecated?: false, disabled?: true)
allow(Formulary).to receive(:factory).with("homebrew/foo/gone").and_return(formula) 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
end end
@@ -145,35 +145,35 @@ RSpec.describe Homebrew::Cmd::TapInfo do
end end
it "does not mark an uninstalled cask" do 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 end
it "marks an installed cask as satisfied" do it "marks an installed cask as satisfied" do
cask = instance_double(Cask::Cask, outdated?: false, deprecated?: false, disabled?: false) cask = instance_double(Cask::Cask, outdated?: false, deprecated?: false, disabled?: false)
allow(Cask::CaskLoader).to receive(:load).with("homebrew/foo/installed").and_return(cask) 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 end
it "marks an outdated installed cask as upgradable" do it "marks an outdated installed cask as upgradable" do
cask = instance_double(Cask::Cask, outdated?: true, deprecated?: false, disabled?: false) cask = instance_double(Cask::Cask, outdated?: true, deprecated?: false, disabled?: false)
allow(Cask::CaskLoader).to receive(:load).with("homebrew/foo/outdated").and_return(cask) 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 end
it "marks a deprecated cask with `(deprecated)`" do it "marks a deprecated cask with `(deprecated)`" do
cask = instance_double(Cask::Cask, outdated?: false, deprecated?: true, disabled?: false) cask = instance_double(Cask::Cask, outdated?: false, deprecated?: true, disabled?: false)
allow(Cask::CaskLoader).to receive(:load).with("homebrew/foo/old").and_return(cask) 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 end
it "marks a disabled cask with `(disabled)`" do it "marks a disabled cask with `(disabled)`" do
cask = instance_double(Cask::Cask, outdated?: false, deprecated?: false, disabled?: true) cask = instance_double(Cask::Cask, outdated?: false, deprecated?: false, disabled?: true)
allow(Cask::CaskLoader).to receive(:load).with("homebrew/foo/gone").and_return(cask) 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
end end
@@ -218,11 +218,11 @@ RSpec.describe Homebrew::Cmd::TapInfo do
end end
it "lists every formula and cask, marking only the installed ones" do 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( .to output(
/Commands.*mycmd.*==> Formulae.*foo.*✔.*uninstalled-formula.*==> Casks.*bar.*✔.*uninstalled-cask/m, /Commands.*mycmd.*==> Formulae.*foo.*✔.*uninstalled-formula.*==> Casks.*bar.*✔.*uninstalled-cask/m,
).to_stdout ).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
end end
@@ -249,7 +249,7 @@ RSpec.describe Homebrew::Cmd::TapInfo do
end end
it "warns about truncation and shows only installed entries under the standard header" do 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 .to output(/==> Formulae.*formula7.*✔/m).to_stdout
.and output(/Tap has more than 30 formulae; showing only installed entries\./).to_stderr .and output(/Tap has more than 30 formulae; showing only installed entries\./).to_stderr
end end
@@ -273,9 +273,9 @@ RSpec.describe Homebrew::Cmd::TapInfo do
end end
it "lists every formula and cask without uninstalled markers" do 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 .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
end end
@@ -298,13 +298,13 @@ RSpec.describe Homebrew::Cmd::TapInfo do
end end
it "shows a link to the tap remote and warns when nothing is installed" do 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 .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 .and output(/Tap has more than 30 formulae and none are installed\./).to_stderr
end end
it "does not list individual formula names" do 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 .not_to output(/formula1\b/).to_stdout
end end
end end
+13 -12
View File
@@ -15,7 +15,7 @@ RSpec.describe Homebrew::Cmd::UpdateReport do
it "links to the donations section" do it "links to the donations section" do
allow(Homebrew::Settings).to receive(:read).with("donationmessage").and_return("false") 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 .to output(include("https://github.com/Homebrew/brew#-donations")).to_stdout
end 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(:A)).to be_empty
expect(hub.select_formula_or_cask(:D)).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 end
context "when updating a Tap other than the core Tap" do 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(:A)).to be_empty
expect(hub.select_formula_or_cask(:D)).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 end
specify "with renamed Formula and restructured Tap" do 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(:A)).to be_empty
expect(hub.select_formula_or_cask(:D)).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 end
specify "with simulated 'homebrew/php' restructuring" do 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(:A)).to be_empty
expect(hub.select_formula_or_cask(:D)).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 end
specify "with Formula changes" do 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(:A)).to eq(%w[foo/bar/lua])
expect(hub.select_formula_or_cask(:M)).to eq(%w[foo/bar/git]) 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 end
specify "with formula migrated to cask in same tap" do 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 it "recommends trusting just the migrated package then migrating a rename" do
expect(other_tap).not_to receive(:ensure_installed!) 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 .to output(%r{brew trust foo/bar/newfoo.*brew migrate oldfoo}m).to_stderr
end end
it "recommends a reinstall for an unchanged-name tap migration" do 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 .to output(/brew reinstall foo/).to_stderr
end end
it "taps a trusted tap" do it "taps a trusted tap" do
allow(other_tap).to receive(:official?).and_return(true) allow(other_tap).to receive(:official?).and_return(true)
expect(other_tap).to receive(:ensure_installed!) 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
end end
@@ -577,7 +577,7 @@ RSpec.describe Homebrew::Cmd::UpdateReport do
-baz -baz
DIFF DIFF
expect(reporter.send(:diff)).to eq(<<~DIFF.strip) expect(reporter.diff).to eq(<<~DIFF.strip)
A api/bar.rb A api/bar.rb
D api/baz.rb D api/baz.rb
DIFF DIFF
@@ -593,7 +593,7 @@ RSpec.describe Homebrew::Cmd::UpdateReport do
-baz -baz
DIFF DIFF
expect(reporter.send(:diff)).to eq(<<~DIFF.strip) expect(reporter.diff).to eq(<<~DIFF.strip)
A api/baz.rb A api/baz.rb
DIFF DIFF
end end
@@ -698,13 +698,14 @@ RSpec.describe Homebrew::Cmd::UpdateReport do
end end
it "merges frozen report arrays" do 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 }) first_reporter = instance_double(Reporter, report: { A: ["foo"].freeze })
second_reporter = instance_double(Reporter, report: { A: ["bar"] }) second_reporter = instance_double(Reporter, report: { A: ["bar"] })
hub.add(first_reporter) hub.add(first_reporter)
hub.add(second_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 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")) allow(Cask::Upgrade).to receive(:upgrade_casks!).and_raise(Cask::CaskError.new("test cask error"))
cmd = described_class.new(["--cask"]) cmd = described_class.new(["--cask"])
expect { cmd.send(:upgrade_outdated_casks!, []) } expect { cmd.upgrade_outdated_casks!([]) }
.to output(/test cask error/).to_stderr .to output(/test cask error/).to_stderr
expect(Homebrew).to have_failed expect(Homebrew).to have_failed
@@ -374,7 +374,7 @@ RSpec.describe Homebrew::Cmd::UpgradeCmd do
expect(Homebrew::Install).not_to receive(:ask_casks) expect(Homebrew::Install).not_to receive(:ask_casks)
expect(Cask::Upgrade).to receive(:upgrade_casks!).and_return(true) expect(Cask::Upgrade).to receive(:upgrade_casks!).and_return(true)
cmd.send(:upgrade_outdated_casks!, []) cmd.upgrade_outdated_casks!([])
end end
it "passes --no-quit to cask upgrades" do it "passes --no-quit to cask upgrades" do
@@ -385,7 +385,7 @@ RSpec.describe Homebrew::Cmd::UpgradeCmd do
true true
end end
cmd.send(:upgrade_outdated_casks!, []) cmd.upgrade_outdated_casks!([])
end end
it "passes HOMEBREW_NO_UPGRADE_QUIT_CASKS to cask upgrades" do it "passes HOMEBREW_NO_UPGRADE_QUIT_CASKS to cask upgrades" do
@@ -397,7 +397,7 @@ RSpec.describe Homebrew::Cmd::UpgradeCmd do
true true
end end
cmd.send(:upgrade_outdated_casks!, []) cmd.upgrade_outdated_casks!([])
end end
end end
@@ -409,7 +409,7 @@ RSpec.describe Homebrew::Cmd::UpgradeCmd do
expect(cmd).to receive(:upgrade_outdated_formulae!) expect(cmd).to receive(:upgrade_outdated_formulae!)
.with([], dry_run: true, show_upgrade_summary: false) .with([], dry_run: true, show_upgrade_summary: false)
.ordered do .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 true
end end
expect(cmd).to receive(:upgrade_outdated_casks!) expect(cmd).to receive(:upgrade_outdated_casks!)
@@ -531,7 +531,7 @@ RSpec.describe Homebrew::Cmd::UpgradeCmd do
expect(cmd).to receive(:upgrade_outdated_formulae!) expect(cmd).to receive(:upgrade_outdated_formulae!)
.with([formula], dry_run: true, show_upgrade_summary: false) .with([formula], dry_run: true, show_upgrade_summary: false)
.ordered do .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 true
end end
allow(cmd).to receive(:show_final_upgrade_summary).and_call_original 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(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) 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)"]) .to eq(["testball 0.1 -> 0.2 (500B)"])
end end
@@ -578,7 +578,7 @@ RSpec.describe Homebrew::Cmd::UpgradeCmd do
allow(Keg).to receive(:new).with(HOMEBREW_PREFIX/"opt/testball").and_return(keg) allow(Keg).to receive(:new).with(HOMEBREW_PREFIX/"opt/testball").and_return(keg)
expect(bottle).not_to receive(:fetch_tab) 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"]) .to eq(["testball 0.1 -> 0.2"])
end end
@@ -697,7 +697,7 @@ RSpec.describe Homebrew::Cmd::UpgradeCmd do
allow(cmd).to receive(:final_upgrade_summary).and_return(summary) 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 ==> Would upgrade 2 outdated packages
testball 0.1 -> 0.2 (500B) testball 0.1 -> 0.2 (500B)
codex 1.0 -> 2.0 codex 1.0 -> 2.0
@@ -784,7 +784,7 @@ RSpec.describe Homebrew::Cmd::UpgradeCmd do
use_prefetched: false, prefetch_names: nil, use_prefetched: false, prefetch_names: nil,
prefetch_upgrades: nil, **| prefetch_upgrades: nil, **|
if dry_run 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 elsif prefetch_only
prefetch_names&.replace(["deno"]) prefetch_names&.replace(["deno"])
prefetch_upgrades&.replace(["deno 2.7.10 -> 2.7.11"]) 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).not_to receive(:ofail)
expect( expect(
cmd.send( cmd.prefetch_outdated_casks!(
:prefetch_outdated_casks!,
[], [],
download_queue:, download_queue:,
prefetch_names:, prefetch_names:,
@@ -1019,7 +1018,7 @@ RSpec.describe Homebrew::Cmd::UpgradeCmd do
allow(Homebrew::Upgrade).to receive(:formula_installers).and_return([]) allow(Homebrew::Upgrade).to receive(:formula_installers).and_return([])
expect do 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.to output("==> Downloading bottle manifests\n").to_stdout
end end
@@ -1042,7 +1041,7 @@ RSpec.describe Homebrew::Cmd::UpgradeCmd do
allow(Homebrew::Upgrade).to receive(:formula_installers).and_return([]) allow(Homebrew::Upgrade).to receive(:formula_installers).and_return([])
expect do 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.not_to output(/Downloading bottle manifests/).to_stdout
end end
@@ -1116,7 +1115,7 @@ RSpec.describe Homebrew::Cmd::UpgradeCmd do
cmd = described_class.new(["--cask", "--dry-run"]) 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 .to not_to_output(/Unexpected method 'discontinued' called during caveats on Cask local-caffeine\./).to_stderr
end end
@@ -1133,7 +1132,7 @@ RSpec.describe Homebrew::Cmd::UpgradeCmd do
allow(cmd).to receive(:final_upgrade_summary).and_return(summary) 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 ==> Upgraded 1 outdated package
testball 0.1 -> 0.2 testball 0.1 -> 0.2
==> 1 Pinned formula ==> 1 Pinned formula
@@ -1188,8 +1187,8 @@ RSpec.describe Homebrew::Cmd::UpgradeCmd do
pinned_formulae: [pinned], pinned_formulae: [pinned],
) )
cmd.send(:record_formula_upgrade_summary, context) cmd.record_formula_upgrade_summary(context)
summary = cmd.send(:final_upgrade_summary) summary = cmd.final_upgrade_summary
expect(summary.version_changes).to include("testball 0.1 -> 0.2") expect(summary.version_changes).to include("testball 0.1 -> 0.2")
expect(summary.pinned_formulae).to include("pinnedball 1.0") expect(summary.pinned_formulae).to include("pinnedball 1.0")
@@ -1224,9 +1223,9 @@ RSpec.describe Homebrew::Cmd::UpgradeCmd do
end end
allow(Homebrew::Upgrade).to receive(:upgrade_dependents) 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 end
it "omits failed formula version changes from the final summary" do 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_formulae).and_return([successful_formula_installer])
allow(Homebrew::Upgrade).to receive(:upgrade_dependents) 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"), version_changes: contain_exactly("testball 0.1 -> 0.2"),
deprecated: contain_exactly("failball"), deprecated: contain_exactly("failball"),
) )
@@ -174,6 +174,6 @@ RSpec.describe Dependency do
allow(foo).to receive(:to_formula).and_raise(FormulaUnavailableError, foo.name) allow(foo).to receive(:to_formula).and_raise(FormulaUnavailableError, foo.name)
f = instance_double(Formula, name: "f", deps: [foo]) f = instance_double(Formula, name: "f", deps: [foo])
expect { described_class.expand(f) }.to raise_error(FormulaUnavailableError) 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
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 context "when cask does not have on_system blocks/calls or `depends_on arch`" do
it "returns an array only including macOS/ARM" do it "returns an array only including macOS/ARM" do
Homebrew::SimulateSystem.with(os: :linux) 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]]) .to eq([[newest_macos, :arm]])
end end
Homebrew::SimulateSystem.with(os: older_macos) do 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]]) .to eq([[older_macos, :arm]])
end end
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 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 it "returns an array only including macOS/`depends_on arch` value" do
Homebrew::SimulateSystem.with(os: :linux, arch: :arm) 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]]) .to eq([[newest_macos, :intel]])
end end
Homebrew::SimulateSystem.with(os: older_macos, arch: :arm) do 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]]) .to eq([[older_macos, :intel]])
end end
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 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 it "returns an array with combinations of `OnSystem::BASE_OS_OPTIONS` and `OnSystem::ARCH_OPTIONS`" do
Homebrew::SimulateSystem.with(os: :linux) 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([ .to eq([
[newest_macos, :intel], [newest_macos, :intel],
[newest_macos, :arm], [newest_macos, :arm],
@@ -123,7 +123,7 @@ RSpec.describe Homebrew::DevCmd::BumpCaskPr do
end end
Homebrew::SimulateSystem.with(os: older_macos) do 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([ .to eq([
[older_macos, :intel], [older_macos, :intel],
[older_macos, :arm], [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 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 it "returns an array with combinations of `OnSystem::BASE_OS_OPTIONS` and `OnSystem::ARCH_OPTIONS`" do
Homebrew::SimulateSystem.with(os: :linux, arch: :arm) 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([ .to eq([
[newest_macos, :intel], [newest_macos, :intel],
[newest_macos, :arm], [newest_macos, :arm],
@@ -147,7 +147,7 @@ RSpec.describe Homebrew::DevCmd::BumpCaskPr do
end end
Homebrew::SimulateSystem.with(os: older_macos, arch: :arm) do 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([ .to eq([
[older_macos, :intel], [older_macos, :intel],
[older_macos, :arm], [older_macos, :arm],
@@ -177,7 +177,7 @@ RSpec.describe Homebrew::DevCmd::BumpCaskPr do
end end
expect(cask.depends_on.arch).to eq([{ type: :arm, bits: 64 }]) 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([ .to eq([
[older_macos, :intel], [older_macos, :intel],
[older_macos, :arm], [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 it "returns an array only using archs of arch-specific versions" do
Homebrew::SimulateSystem.with(os: :linux) 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([ .to eq([
[newest_macos, :arm], [newest_macos, :arm],
[:linux, :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([ .to eq([
[newest_macos, :intel], [newest_macos, :intel],
[:linux, :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([ .to eq([
[newest_macos, :arm], [newest_macos, :arm],
[newest_macos, :intel], [newest_macos, :intel],
[:linux, :arm], [:linux, :arm],
[:linux, :intel], [: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([ .to eq([
[newest_macos, :intel], [newest_macos, :intel],
[newest_macos, :arm], [newest_macos, :arm],
@@ -223,24 +223,24 @@ RSpec.describe Homebrew::DevCmd::BumpCaskPr do
end end
Homebrew::SimulateSystem.with(os: older_macos) do 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([ .to eq([
[older_macos, :arm], [older_macos, :arm],
[:linux, :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([ .to eq([
[older_macos, :intel], [older_macos, :intel],
[:linux, :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([ .to eq([
[older_macos, :arm], [older_macos, :arm],
[older_macos, :intel], [older_macos, :intel],
[:linux, :arm], [:linux, :arm],
[:linux, :intel], [: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([ .to eq([
[older_macos, :intel], [older_macos, :intel],
[older_macos, :arm], [older_macos, :arm],
@@ -274,14 +274,14 @@ RSpec.describe Homebrew::DevCmd::BumpCaskPr do
end end
it "is idempotent when the replacement has already been applied" do 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(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 .not_to raise_error
end end
it "raises when the stanza is missing entirely" do 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/) .to raise_error(/Could not find 'version' stanza/)
end end
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) } cask = Homebrew::SimulateSystem.with(os: newest_macos, arch: :arm) { cask_from_contents(contents) }
new_version = Homebrew::BumpVersionParser.new(arm: "2.0") 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 .to eq <<~RUBY
cask "foo" do cask "foo" do
on_arm 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) } cask = Homebrew::SimulateSystem.with(os: newest_macos, arch: :arm) { cask_from_contents(contents) }
new_version = Homebrew::BumpVersionParser.new(arm: "2.0") 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 .to eq <<~RUBY
cask "foo" do cask "foo" do
arch arm: "arm", intel: "intel" arch arm: "arm", intel: "intel"
@@ -388,7 +388,7 @@ RSpec.describe Homebrew::DevCmd::BumpCaskPr do
cask = cask_from_contents(contents) cask = cask_from_contents(contents)
new_version = Homebrew::BumpVersionParser.new(arm: "2.0") 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 .to eq <<~RUBY
cask "foo" do cask "foo" do
on_arm 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") new_version = Homebrew::BumpVersionParser.new(arm: "2.0", intel: "1.5")
expect( 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 ).to eq <<~RUBY
cask "foo" do cask "foo" do
on_arm do on_arm do
@@ -462,7 +462,7 @@ RSpec.describe Homebrew::DevCmd::BumpCaskPr do
new_version = Homebrew::BumpVersionParser.new(arm: "2.0") new_version = Homebrew::BumpVersionParser.new(arm: "2.0")
expect( 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 ).to eq <<~RUBY
cask "foo" do cask "foo" do
on_arm do on_arm do
@@ -502,7 +502,7 @@ RSpec.describe Homebrew::DevCmd::BumpCaskPr do
new_version = Homebrew::BumpVersionParser.new(general: "2.0") new_version = Homebrew::BumpVersionParser.new(general: "2.0")
expect( 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 ).to eq <<~RUBY
cask "foo" do cask "foo" do
on_arm do on_arm do
@@ -544,7 +544,7 @@ RSpec.describe Homebrew::DevCmd::BumpCaskPr do
new_version = Homebrew::BumpVersionParser.new(arm: "2.0") new_version = Homebrew::BumpVersionParser.new(arm: "2.0")
expect( 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) ).to eq(contents)
end end
end end
@@ -602,14 +602,14 @@ RSpec.describe Homebrew::DevCmd::BumpCaskPr do
context "when cask is not in a tap" do context "when cask is not in a tap" do
it "outputs nothing" 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
end end
context "when a livecheck throttle value isn't present" do context "when a livecheck throttle value isn't present" do
it "does not throttle" do it "does not throttle" do
allow(c).to receive(:tap).and_return(tap) 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
end end
@@ -623,7 +623,7 @@ RSpec.describe Homebrew::DevCmd::BumpCaskPr do
it "does not throttle" do it "does not throttle" do
allow(c_throttle).to receive(:tap).and_return(tap) allow(c_throttle).to receive(:tap).and_return(tap)
expect do 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.not_to output.to_stderr
end end
end end
@@ -632,7 +632,7 @@ RSpec.describe Homebrew::DevCmd::BumpCaskPr do
it "does not throttle" do it "does not throttle" do
allow(c_throttle).to receive(:tap).and_return(tap) allow(c_throttle).to receive(:tap).and_return(tap)
expect do 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.not_to output.to_stderr
end end
end end
@@ -643,7 +643,7 @@ RSpec.describe Homebrew::DevCmd::BumpCaskPr do
it "throttles version" do it "throttles version" do
allow(c_throttle).to receive(:tap).and_return(tap) allow(c_throttle).to receive(:tap).and_return(tap)
expect do 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 rescue SystemExit
next next
end.to output(throttle_error).to_stderr 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) allow(Homebrew::Livecheck).to receive(:throttle_interval_elapsed?).and_return(false)
expect do 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 rescue SystemExit
next next
end.to output(throttle_rate_days_error).to_stderr 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) allow(Homebrew::Livecheck).to receive(:throttle_interval_elapsed?).and_return(true)
expect do 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.not_to output.to_stderr
end end
end end
@@ -685,7 +685,7 @@ RSpec.describe Homebrew::DevCmd::BumpCaskPr do
allow(Homebrew::Livecheck).to receive(:throttle_interval_elapsed?).and_return(false) allow(Homebrew::Livecheck).to receive(:throttle_interval_elapsed?).and_return(false)
expect do 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 rescue SystemExit
next next
end.to output(throttle_days_error).to_stderr 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) allow(Homebrew::Livecheck).to receive(:throttle_interval_elapsed?).and_return(true)
expect do 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.not_to output.to_stderr
end end
end end
@@ -108,7 +108,7 @@ RSpec.describe Homebrew::DevCmd::BumpFormulaPr do
it "outputs nothing" do it "outputs nothing" do
allow(f).to receive(:tap).and_return(nil) 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
end end
@@ -116,7 +116,7 @@ RSpec.describe Homebrew::DevCmd::BumpFormulaPr do
it "does not throttle" do it "does not throttle" do
allow(f).to receive(:tap).and_return(tap) 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
end end
@@ -124,7 +124,7 @@ RSpec.describe Homebrew::DevCmd::BumpFormulaPr do
it "does not throttle" do it "does not throttle" do
allow(f_throttle).to receive(:tap).and_return(tap) 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
end end
@@ -133,7 +133,7 @@ RSpec.describe Homebrew::DevCmd::BumpFormulaPr do
allow(f_throttle).to receive(:tap).and_return(tap) allow(f_throttle).to receive(:tap).and_return(tap)
expect do 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 rescue SystemExit
nil nil
end.to output(throttle_error).to_stderr 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) allow(Homebrew::Livecheck).to receive(:throttle_interval_elapsed?).and_return(false)
expect do 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 rescue SystemExit
nil nil
end.to output(throttle_rate_days_error).to_stderr 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 it "does not throttle when throttle interval has elapsed" do
allow(Homebrew::Livecheck).to receive(:throttle_interval_elapsed?).and_return(true) 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
end end
@@ -171,7 +171,7 @@ RSpec.describe Homebrew::DevCmd::BumpFormulaPr do
allow(Homebrew::Livecheck).to receive(:throttle_interval_elapsed?).and_return(false) allow(Homebrew::Livecheck).to receive(:throttle_interval_elapsed?).and_return(false)
expect do 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 rescue SystemExit
next next
end.to output(throttle_days_error).to_stderr 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) allow(Homebrew::Livecheck).to receive(:throttle_interval_elapsed?).and_return(true)
expect do 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.not_to output.to_stderr
end end
end end
@@ -210,13 +210,13 @@ RSpec.describe Homebrew::DevCmd::BumpFormulaPr do
it "only updates `:parent` resource" 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).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 end
it "does not update `:parent` resource if set in `--resource-versions`" do it "does not update `:parent` resource if set in `--resource-versions`" do
resource_versions = { "parent" => { current_version: "1.2.3", latest_version: version } } 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).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
end end
@@ -237,21 +237,21 @@ RSpec.describe Homebrew::DevCmd::BumpFormulaPr do
version = "2.1.0" version = "2.1.0"
resource_versions = { "foo" => { current_version: "1.2.3", latest_version: version } } 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).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 end
it "downgrades to requested version" do it "downgrades to requested version" do
version = "0.1.2" version = "0.1.2"
resource_versions = { "foo" => { current_version: "1.2.3", latest_version: version } } 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).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 end
it "returns update failures" do it "returns update failures" do
version = "0.1.2" version = "0.1.2"
resource_versions = { "foo" => { current_version: "1.2.3", latest_version: version } } 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).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 end
end end

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