From 56f42e0e1d9cd7c280be53dbf7c96ea44b5af6d3 Mon Sep 17 00:00:00 2001 From: Mike McQuaid Date: Fri, 24 Jul 2026 19:55:53 +0100 Subject: [PATCH] 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. --- AGENTS.md | 2 +- Library/.rubocop.yml | 14 + Library/Homebrew/bump.rb | 2 +- Library/Homebrew/bundle/brew.rb | 6 + Library/Homebrew/bundle/parallel_installer.rb | 30 +- Library/Homebrew/bundle/skipper.rb | 9 + Library/Homebrew/cache_store.rb | 3 + .../cask/artifact/abstract_uninstall.rb | 188 +++---- Library/Homebrew/cask/artifact/moved.rb | 10 +- Library/Homebrew/cask/artifact/relocated.rb | 10 +- Library/Homebrew/cask/audit.rb | 245 ++++----- Library/Homebrew/cask/auditor.rb | 4 +- Library/Homebrew/cask/cask_loader.rb | 3 + Library/Homebrew/cask/caskroom.rb | 7 + Library/Homebrew/cask/download.rb | 6 +- Library/Homebrew/cli/parser.rb | 3 + Library/Homebrew/cmd/fetch.rb | 124 ++--- Library/Homebrew/cmd/info.rb | 332 ++++++------- Library/Homebrew/cmd/outdated.rb | 50 +- Library/Homebrew/cmd/search.rb | 30 +- Library/Homebrew/cmd/source.rb | 40 +- Library/Homebrew/cmd/tap-info.rb | 62 +-- Library/Homebrew/cmd/update-report.rb | 22 +- .../Homebrew/cmd/update_report/reporter.rb | 58 +-- .../cmd/update_report/reporter_hub.rb | 5 + Library/Homebrew/cmd/upgrade.rb | 158 +++--- Library/Homebrew/dependency.rb | 3 + Library/Homebrew/dev-cmd/bump-cask-pr.rb | 114 ++--- Library/Homebrew/dev-cmd/bump-formula-pr.rb | 164 +++--- Library/Homebrew/dev-cmd/bump.rb | 468 +++++++++--------- Library/Homebrew/dev-cmd/contributions.rb | 304 ++++++------ .../dev-cmd/generate-cask-ci-matrix.rb | 44 +- Library/Homebrew/dev-cmd/generate-zap.rb | 72 +-- Library/Homebrew/dev-cmd/release.rb | 4 +- Library/Homebrew/dev-cmd/tests.rb | 76 +-- Library/Homebrew/download_queue.rb | 10 +- .../abstract_file_download_strategy.rb | 30 +- .../curl_download_strategy.rb | 52 +- .../git_download_strategy.rb | 48 +- .../github_git_download_strategy.rb | 6 + Library/Homebrew/downloadable.rb | 5 +- .../extend/os/linux/dependency_collector.rb | 10 +- .../Homebrew/extend/os/linux/dev-cmd/tests.rb | 24 +- Library/Homebrew/extend/os/linux/sandbox.rb | 14 +- .../extend/os/linux/sandbox/landlock.rb | 22 +- .../Homebrew/extend/os/mac/utils/bottles.rb | 4 +- Library/Homebrew/formula.rb | 51 +- Library/Homebrew/formula_auditor.rb | 102 ++-- Library/Homebrew/formula_installer.rb | 3 + Library/Homebrew/github_packages.rb | 286 +++++------ Library/Homebrew/github_runner_matrix.rb | 160 +++--- Library/Homebrew/install.rb | 20 +- Library/Homebrew/language/node.rb | 5 + Library/Homebrew/livecheck/livecheck.rb | 8 +- Library/Homebrew/livecheck/strategy/git.rb | 5 + Library/Homebrew/livecheck/strategy/xorg.rb | 5 + Library/Homebrew/macos_version.rb | 6 + Library/Homebrew/os/linux/ld.rb | 5 + Library/Homebrew/os/linux/libstdcxx.rb | 5 + Library/Homebrew/reinstall/reinstall.rb | 4 +- Library/Homebrew/resource.rb | 3 + Library/Homebrew/rubocops/all.rb | 2 + .../no_instance_variable_access_in_tests.rb | 34 ++ Library/Homebrew/rubocops/no_send_in_tests.rb | 73 +++ Library/Homebrew/sandbox.rb | 20 +- Library/Homebrew/tap.rb | 1 - Library/Homebrew/test/api/cask_struct_spec.rb | 12 +- .../Homebrew/test/api/formula_struct_spec.rb | 8 +- Library/Homebrew/test/bump_spec.rb | 7 +- Library/Homebrew/test/bundle/dsl_spec.rb | 17 +- .../Homebrew/test/bundle/installer_spec.rb | 8 +- Library/Homebrew/test/bundle/skipper_spec.rb | 4 +- Library/Homebrew/test/cache_store_spec.rb | 2 +- .../cask/artifact/abstract_uninstall_spec.rb | 10 +- .../Homebrew/test/cask/artifact/app_spec.rb | 6 +- .../artifact/generated_completion_spec.rb | 7 +- .../test/cask/artifact/relocated_spec.rb | 4 +- .../artifact/shared_examples/uninstall_zap.rb | 5 +- .../test/cask/artifact/uninstall_spec.rb | 6 +- Library/Homebrew/test/cask/audit_spec.rb | 8 +- Library/Homebrew/test/cask/auditor_spec.rb | 18 +- .../Homebrew/test/cask/cask_loader_spec.rb | 4 +- Library/Homebrew/test/cask/caskroom_spec.rb | 2 +- Library/Homebrew/test/cask/download_spec.rb | 2 +- .../Homebrew/test/cask/dsl/version_spec.rb | 2 +- Library/Homebrew/test/cask/dsl_spec.rb | 2 +- Library/Homebrew/test/cask/installer_spec.rb | 8 +- .../test/cmd/bundle/check_subcommand_spec.rb | 6 +- Library/Homebrew/test/cmd/fetch_spec.rb | 6 +- Library/Homebrew/test/cmd/info_spec.rb | 102 ++-- Library/Homebrew/test/cmd/outdated_spec.rb | 4 +- Library/Homebrew/test/cmd/search_spec.rb | 10 +- .../test/cmd/shared_examples/args_parse.rb | 2 +- Library/Homebrew/test/cmd/source_spec.rb | 54 +- Library/Homebrew/test/cmd/tap-info_spec.rb | 40 +- .../Homebrew/test/cmd/update-report_spec.rb | 25 +- Library/Homebrew/test/cmd/upgrade_spec.rb | 43 +- .../test/dependency_expansion_spec.rb | 2 +- .../test/dev-cmd/bump-cask-pr_spec.rb | 72 +-- .../test/dev-cmd/bump-formula-pr_spec.rb | 26 +- Library/Homebrew/test/dev-cmd/bump_spec.rb | 95 ++-- .../test/dev-cmd/contributions_spec.rb | 41 +- .../dev-cmd/generate-cask-ci-matrix_spec.rb | 2 +- .../test/dev-cmd/generate-zap_spec.rb | 91 ++-- Library/Homebrew/test/dev-cmd/release_spec.rb | 4 +- Library/Homebrew/test/dev-cmd/tests_spec.rb | 10 +- Library/Homebrew/test/download_queue_spec.rb | 2 +- .../download_strategies/abstract_file_spec.rb | 12 +- .../test/download_strategies/curl_spec.rb | 8 +- .../test/download_strategies/git_spec.rb | 4 +- .../download_strategies/github_git_spec.rb | 4 +- Library/Homebrew/test/formula_auditor_spec.rb | 6 +- .../Homebrew/test/formula_installer_spec.rb | 13 +- Library/Homebrew/test/formula_spec.rb | 28 +- Library/Homebrew/test/github_packages_spec.rb | 94 ++-- .../test/github_runner_matrix_spec.rb | 4 +- Library/Homebrew/test/install_spec.rb | 2 +- Library/Homebrew/test/language/node_spec.rb | 4 +- .../test/language/python/virtualenv_spec.rb | 2 +- .../Homebrew/test/livecheck/livecheck_spec.rb | 38 +- .../test/livecheck/strategy/git_spec.rb | 4 +- .../test/livecheck/strategy/xorg_spec.rb | 7 +- Library/Homebrew/test/livecheck_spec.rb | 8 +- Library/Homebrew/test/macos_version_spec.rb | 11 +- Library/Homebrew/test/mcp_server_spec.rb | 2 +- .../os/linux/dependency_collector_spec.rb | 2 +- Library/Homebrew/test/os/linux/ld_spec.rb | 2 +- .../Homebrew/test/os/linux/libstdcxx_spec.rb | 2 +- Library/Homebrew/test/pathname_spec.rb | 3 + Library/Homebrew/test/reinstall_spec.rb | 2 +- ..._instance_variable_access_in_tests_spec.rb | 46 ++ .../test/rubocops/no_send_in_tests_spec.rb | 86 ++++ .../Homebrew/test/sandbox_landlock_spec.rb | 10 +- Library/Homebrew/test/sandbox_linux_spec.rb | 4 +- Library/Homebrew/test/sandbox_shared_spec.rb | 38 +- .../test/support/helper/api_hashable.rb | 3 +- .../spec/shared_context/integration_test.rb | 2 +- .../test/support/helper/subcommand_spec.rb | 5 +- Library/Homebrew/test/tab_spec.rb | 2 +- Library/Homebrew/test/tap_spec.rb | 4 +- .../test/test_bot/formulae_dependents_spec.rb | 7 +- .../Homebrew/test/test_bot/formulae_spec.rb | 26 +- .../test/test_bot/test_formulae_spec.rb | 5 +- Library/Homebrew/test/test_bot/test_spec.rb | 4 +- .../Homebrew/test/test_runner_formula_spec.rb | 2 +- .../Homebrew/test/unpack_strategy/dmg_spec.rb | 2 +- Library/Homebrew/test/upgrade_spec.rb | 2 +- .../Homebrew/test/utils/autoremove_spec.rb | 18 +- .../test/utils/bottles/collector_spec.rb | 12 +- Library/Homebrew/test/utils/shell_spec.rb | 20 +- Library/Homebrew/test/utils/svn_spec.rb | 3 +- Library/Homebrew/test/utils/tar_spec.rb | 3 +- Library/Homebrew/test_bot/formulae.rb | 402 +++++++-------- .../Homebrew/test_bot/formulae_dependents.rb | 142 +++--- Library/Homebrew/test_bot/test.rb | 60 +-- Library/Homebrew/test_bot/test_formulae.rb | 151 +++--- Library/Homebrew/unpack_strategy/dmg.rb | 26 +- Library/Homebrew/upgrade.rb | 4 +- Library/Homebrew/utils/autoremove.rb | 2 - Library/Homebrew/utils/bottles.rb | 12 +- 160 files changed, 3076 insertions(+), 2763 deletions(-) create mode 100644 Library/Homebrew/rubocops/no_instance_variable_access_in_tests.rb create mode 100644 Library/Homebrew/rubocops/no_send_in_tests.rb create mode 100644 Library/Homebrew/test/rubocops/no_instance_variable_access_in_tests_spec.rb create mode 100644 Library/Homebrew/test/rubocops/no_send_in_tests_spec.rb diff --git a/AGENTS.md b/AGENTS.md index 01bf823e03..e53de9a026 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -64,6 +64,6 @@ Do not use conventional commit prefixes such as `feat:`, `fix:`, `chore:`, `refa 9. Inline new or existing methods as methods or local variables unless they are reused 2+ times or needed for unit tests. 10. Avoid `T.must`, `T.cast`, `T.let`, `T.untyped` and `T.anything` where possible while maintaining `typed: strict`; prefer explicit nil checks, precise types and APIs that return non-nil values. If a generic top type is unavoidable, prefer `T.anything` over `T.untyped`. 11. Avoid `T.unsafe(self)` whenever possible; prefer `requires_ancestor` or similar typed module patterns. -12. Prefer `.public_send` over `.send` where possible; call methods directly when practical. Use `.send` only when a private API must be invoked. +12. Prefer `.public_send` over `.send` where possible; call methods directly when practical. Use `.send` only when a private API must be invoked. In tests, never use `.send`: make the method public and call it directly (enforced by `Homebrew/NoSendInTests`), keeping `.public_send` only for dynamically-named public methods. Likewise in tests read and write state through public `attr_*` accessors rather than `instance_variable_get`/`instance_variable_set` (enforced by `Homebrew/NoInstanceVariableAccessInTests`). 13. Keep `extend/os/*` prepends as thin as possible; put the `prepend` in the OS-specific `linux` or `macos` file rather than the shared `extend/os/*` loader with an inline `if`, and prefer putting substantive logic in shared code outside `extend/` when practical so it can be tested on all platforms instead of relying on `:needs_linux` or `:needs_macos` specs. 14. When Bash logic mirrors Ruby logic, keep both implementations in sync and add two-way comments naming the matching Ruby and Bash locations; keep matching helper filenames aligned where practical. diff --git a/Library/.rubocop.yml b/Library/.rubocop.yml index 66b4dc5f2e..6b8e31e2c4 100644 --- a/Library/.rubocop.yml +++ b/Library/.rubocop.yml @@ -94,6 +94,20 @@ Homebrew/NegateInclude: - "Homebrew/rubocops/**/*" - "Homebrew/sorbet/tapioca/**/*" +Homebrew/NoInstanceVariableAccessInTests: + Description: "Use public `attr_*` accessors instead of `instance_variable_get`/`instance_variable_set` in tests." + Include: + - "Homebrew/test/**/*.rb" + Exclude: + - "Homebrew/test/support/fixtures/**/*" + +Homebrew/NoSendInTests: + Description: "Make the target method public and call it directly instead of using `send` in tests." + Include: + - "Homebrew/test/**/*.rb" + Exclude: + - "Homebrew/test/support/fixtures/**/*" + Homebrew/UnreferencedLet: Description: "Removes a lazy `let` whose name is never referenced (its block never runs)." Include: diff --git a/Library/Homebrew/bump.rb b/Library/Homebrew/bump.rb index c77f861ac2..85edfb18d7 100644 --- a/Library/Homebrew/bump.rb +++ b/Library/Homebrew/bump.rb @@ -147,7 +147,7 @@ module Homebrew # Redact any token `add_auth_token_to_url!` embedded, so dry-run output doesn't leak it. sig { params(url: T.nilable(String)).returns(String) } - private_class_method def self.redacted_url(url) + def self.redacted_url(url) Formatter.redact_secrets(url.to_s, [GitHub::API.credentials].compact) end diff --git a/Library/Homebrew/bundle/brew.rb b/Library/Homebrew/bundle/brew.rb index 457a7c1a2b..55477fb47c 100644 --- a/Library/Homebrew/bundle/brew.rb +++ b/Library/Homebrew/bundle/brew.rb @@ -43,6 +43,12 @@ module Homebrew @formula_oldnames = T.let(nil, T.nilable(T::Hash[String, String])) end + sig { + params(formulae_by_name: T.nilable(T::Hash[String, T::Hash[Symbol, T.untyped]])) + .returns(T.nilable(T::Hash[String, T::Hash[Symbol, T.untyped]])) + } + attr_writer :formulae_by_name + sig { override.params(name: String, no_upgrade: T::Boolean, verbose: T::Boolean, options: T.untyped).returns(T::Boolean) } def preinstall!(name, no_upgrade: false, verbose: false, **options) new(name, options).preinstall!(no_upgrade:, verbose:) diff --git a/Library/Homebrew/bundle/parallel_installer.rb b/Library/Homebrew/bundle/parallel_installer.rb index b3c90f6105..c023f19b52 100644 --- a/Library/Homebrew/bundle/parallel_installer.rb +++ b/Library/Homebrew/bundle/parallel_installer.rb @@ -102,8 +102,6 @@ module Homebrew @pool.wait_for_termination end - private - sig { params(entries: T::Array[Installer::InstallableEntry]).returns(T::Hash[String, T::Set[String]]) } def build_dependency_map(entries) installed_taps = Homebrew::Bundle::Tap.installed_taps @@ -177,6 +175,21 @@ module Homebrew end end + sig { params(message: String, stream: IO).void } + def write_output(message, stream: $stdout) + @output_mutex.synchronize do + # Interactive installers can leave ONLCR disabled, so use CRLF to + # ensure terminal status output returns to column 0. + if stream.tty? + stream.write(message, "\r\n") + else + stream.puts(message) + end + end + end + + private + sig { params(name: String).returns(String) } def normalize_formula_name(name) Utils.name_from_full_name(name) @@ -286,19 +299,6 @@ module Homebrew end end - sig { params(message: String, stream: IO).void } - def write_output(message, stream: $stdout) - @output_mutex.synchronize do - # Interactive installers can leave ONLCR disabled, so use CRLF to - # ensure terminal status output returns to column 0. - if stream.tty? - stream.write(message, "\r\n") - else - stream.puts(message) - end - end - end - sig { void } def clear_tty_line File.open("/dev/tty", "w") do |f| diff --git a/Library/Homebrew/bundle/skipper.rb b/Library/Homebrew/bundle/skipper.rb index a6fa8bfc37..b69b0bbd49 100644 --- a/Library/Homebrew/bundle/skipper.rb +++ b/Library/Homebrew/bundle/skipper.rb @@ -34,6 +34,15 @@ module Homebrew @failed_taps << tap_name end + sig { params(failed_taps: T.nilable(T::Array[String])).returns(T.nilable(T::Array[String])) } + attr_writer :failed_taps + + sig { + params(skipped_entries: T.nilable(T::Hash[Symbol, T.nilable(T::Array[String])])) + .returns(T.nilable(T::Hash[Symbol, T.nilable(T::Array[String])])) + } + attr_writer :skipped_entries + private sig { returns(T::Hash[Symbol, T.nilable(T::Array[String])]) } diff --git a/Library/Homebrew/cache_store.rb b/Library/Homebrew/cache_store.rb index 531c9fb8f2..6531292951 100644 --- a/Library/Homebrew/cache_store.rb +++ b/Library/Homebrew/cache_store.rb @@ -133,6 +133,9 @@ class CacheStoreDatabase db.each_key(&block) end + sig { params(db: T.nilable(T::Hash[Key, Value])).void } + attr_writer :db + private # Lazily loaded database in read/write mode. If this method is called, a diff --git a/Library/Homebrew/cask/artifact/abstract_uninstall.rb b/Library/Homebrew/cask/artifact/abstract_uninstall.rb index 73d9e78bec..091d037201 100644 --- a/Library/Homebrew/cask/artifact/abstract_uninstall.rb +++ b/Library/Homebrew/cask/artifact/abstract_uninstall.rb @@ -76,6 +76,100 @@ module Cask @bundle_ids_to_reopen ||= T.let([], T.nilable(T::Array[String])) end + # :quit/:signal must come before :kext so the kext will not be in use by a running process + sig { + params( + bundle_ids: String, + command: T.nilable(T.class_of(SystemCommand)), + upgrade: T::Boolean, + _kwargs: T.anything, + ).void + } + def uninstall_quit(*bundle_ids, command: nil, upgrade: false, **_kwargs) + bundle_ids.each do |bundle_id| + next unless running?(bundle_id) + + unless T.must(User.current).gui? + opoo "Not logged into a GUI; skipping quitting application ID '#{bundle_id}'." + next + end + + ohai "Quitting application '#{bundle_id}'..." + + quit_succeeded = T.let(false, T::Boolean) + begin + Timeout.timeout(10) do + Kernel.loop do + next unless quit(bundle_id).success? + + next if running?(bundle_id) + + puts "Application '#{bundle_id}' quit successfully." + quit_succeeded = true + break + end + end + rescue Timeout::Error + opoo "Application '#{bundle_id}' did not quit. #{automation_access_instructions}" + end + + bundle_ids_to_reopen << bundle_id if upgrade && quit_succeeded + end + end + + # This returns T::Enumerable[[Pathname, T::Array[Pathname]]] when called without a block, + # but sorbet doesn't support overloads. + sig { + params( + action: Symbol, + paths: T::Array[T.any(Pathname, String)], + _block: T.nilable(T.proc.params(path: T.any(Pathname, String), resolved_paths: T::Array[Pathname]).void), + ).returns(T.untyped) + } + def each_resolved_path(action, paths, &_block) + return enum_for(:each_resolved_path, action, paths) unless block_given? + + paths.each do |path| + resolved_path = Pathname.new(path.to_s.sub(%r{^~(?=(/|$))}, Dir.home)) + + if resolved_path.relative? + opoo "Skipping #{Formatter.identifier(action)} for relative path '#{path}'." + next + end + + if resolved_path.each_filename.any? { |part| [".", ".."].include?(part) } + opoo "Skipping #{Formatter.identifier(action)} for path with relative segments '#{path}'." + next + end + + begin + resolved_paths = Pathname.glob(resolved_path).reject do |target| + next false unless undeletable?(target) + + opoo "Skipping #{Formatter.identifier(action)} for undeletable path '#{target}'." + true + end + yield path, resolved_paths + rescue Errno::EPERM + raise if ::Cask::Utils.full_disk_access_enabled? + + odie "Unable to remove some files. Please enable Full Disk Access for your terminal under " \ + "#{::Cask::Utils.privacy_security_preference_pane("Full Disk Access")}." + end + end + end + + sig { params(search: String).returns(T::Array[String]) } + def find_launchctl_with_wildcard(search) + regex = Regexp.escape(search).gsub("\\*", ".*") + system_command!("/bin/launchctl", args: ["list"]) + .stdout.lines.drop(1) # skip stdout column headers + .filter_map do |line| + pid, _state, id = line.chomp.split(/\s+/) + id if pid.to_i.nonzero? && T.must(id).match?(regex) + end + end + private sig { params(options: DirectivesType).void } @@ -189,17 +283,6 @@ module Cask end end - sig { params(search: String).returns(T::Array[String]) } - def find_launchctl_with_wildcard(search) - regex = Regexp.escape(search).gsub("\\*", ".*") - system_command!("/bin/launchctl", args: ["list"]) - .stdout.lines.drop(1) # skip stdout column headers - .filter_map do |line| - pid, _state, id = line.chomp.split(/\s+/) - id if pid.to_i.nonzero? && T.must(id).match?(regex) - end - end - sig { returns(String) } def automation_access_instructions <<~EOS @@ -209,47 +292,6 @@ module Cask EOS end - # :quit/:signal must come before :kext so the kext will not be in use by a running process - sig { - params( - bundle_ids: String, - command: T.nilable(T.class_of(SystemCommand)), - upgrade: T::Boolean, - _kwargs: T.anything, - ).void - } - def uninstall_quit(*bundle_ids, command: nil, upgrade: false, **_kwargs) - bundle_ids.each do |bundle_id| - next unless running?(bundle_id) - - unless T.must(User.current).gui? - opoo "Not logged into a GUI; skipping quitting application ID '#{bundle_id}'." - next - end - - ohai "Quitting application '#{bundle_id}'..." - - quit_succeeded = T.let(false, T::Boolean) - begin - Timeout.timeout(10) do - Kernel.loop do - next unless quit(bundle_id).success? - - next if running?(bundle_id) - - puts "Application '#{bundle_id}' quit successfully." - quit_succeeded = true - break - end - end - rescue Timeout::Error - opoo "Application '#{bundle_id}' did not quit. #{automation_access_instructions}" - end - - bundle_ids_to_reopen << bundle_id if upgrade && quit_succeeded - end - end - sig { params(bundle_id: String).returns(T::Boolean) } def running?(bundle_id) script = <<~JAVASCRIPT @@ -452,48 +494,6 @@ module Cask end end - # This returns T::Enumerable[[Pathname, T::Array[Pathname]]] when called without a block, - # but sorbet doesn't support overloads. - sig { - params( - action: Symbol, - paths: T::Array[T.any(Pathname, String)], - _block: T.nilable(T.proc.params(path: T.any(Pathname, String), resolved_paths: T::Array[Pathname]).void), - ).returns(T.untyped) - } - def each_resolved_path(action, paths, &_block) - return enum_for(:each_resolved_path, action, paths) unless block_given? - - paths.each do |path| - resolved_path = Pathname.new(path.to_s.sub(%r{^~(?=(/|$))}, Dir.home)) - - if resolved_path.relative? - opoo "Skipping #{Formatter.identifier(action)} for relative path '#{path}'." - next - end - - if resolved_path.each_filename.any? { |part| [".", ".."].include?(part) } - opoo "Skipping #{Formatter.identifier(action)} for path with relative segments '#{path}'." - next - end - - begin - resolved_paths = Pathname.glob(resolved_path).reject do |target| - next false unless undeletable?(target) - - opoo "Skipping #{Formatter.identifier(action)} for undeletable path '#{target}'." - true - end - yield path, resolved_paths - rescue Errno::EPERM - raise if ::Cask::Utils.full_disk_access_enabled? - - odie "Unable to remove some files. Please enable Full Disk Access for your terminal under " \ - "#{::Cask::Utils.privacy_security_preference_pane("Full Disk Access")}." - end - end - end - sig { params(paths: T.any(Pathname, String), command: T.class_of(SystemCommand), _kwargs: T.anything).void } def uninstall_delete(*paths, command:, **_kwargs) return if paths.empty? diff --git a/Library/Homebrew/cask/artifact/moved.rb b/Library/Homebrew/cask/artifact/moved.rb index b8e597e004..62cc5085ee 100644 --- a/Library/Homebrew/cask/artifact/moved.rb +++ b/Library/Homebrew/cask/artifact/moved.rb @@ -56,6 +56,11 @@ module Cask end end + sig { overridable.params(target: Pathname, source: Pathname).returns(T::Array[T.any(String, Pathname)]) } + def backup_copy_args(target, source) + ["-pR", target, source] + end + private sig { @@ -259,11 +264,6 @@ module Cask def undeletable?(target) !target.parent.writable? end - - sig { overridable.params(target: Pathname, source: Pathname).returns(T::Array[T.any(String, Pathname)]) } - def backup_copy_args(target, source) - ["-pR", target, source] - end end end end diff --git a/Library/Homebrew/cask/artifact/relocated.rb b/Library/Homebrew/cask/artifact/relocated.rb index e9a9b49248..b04ab9b0ed 100644 --- a/Library/Homebrew/cask/artifact/relocated.rb +++ b/Library/Homebrew/cask/artifact/relocated.rb @@ -80,11 +80,6 @@ module Cask "#{@source_string}#{target_string}" end - private - - ALT_NAME_ATTRIBUTE = "com.apple.metadata:kMDItemAlternateNames" - private_constant :ALT_NAME_ATTRIBUTE - # Try to make the asset searchable under the target name. Spotlight # respects this attribute for many filetypes, but ignores it for App # bundles. Alfred 2.2 respects it even for App bundles. @@ -112,6 +107,11 @@ module Cask sudo: !file.writable?) end + private + + ALT_NAME_ATTRIBUTE = "com.apple.metadata:kMDItemAlternateNames" + private_constant :ALT_NAME_ATTRIBUTE + sig { returns(String) } def printable_target target.to_s.sub(/^#{Dir.home}(#{File::SEPARATOR}|$)/, "~/") diff --git a/Library/Homebrew/cask/audit.rb b/Library/Homebrew/cask/audit.rb index 89cbea4412..910e8f70fc 100644 --- a/Library/Homebrew/cask/audit.rb +++ b/Library/Homebrew/cask/audit.rb @@ -36,6 +36,9 @@ module Cask sig { returns(T.nilable(Download)) } attr_reader :download + sig { params(livecheck_result: T.nilable(T.any(T::Boolean, Symbol))).void } + attr_writer :livecheck_result + sig { params( cask: ::Cask::Cask, download: T::Boolean, quarantine: T::Boolean, @@ -148,6 +151,127 @@ module Cask summary.join("\n") end + sig { + params( + include_manual_installers: T::Boolean, + _block: T.nilable(T.proc.params( + arg0: T::Array[T.any(Artifact::Installer, Artifact::Pkg, Artifact::Relocated)], + arg1: Pathname, + ).void), + ).void + } + def extract_artifacts(include_manual_installers: false, &_block) + return unless online? + return if (download = self.download).nil? + + artifacts = cask.artifacts.select do |artifact| + artifact.is_a?(Artifact::Pkg) || + artifact.is_a?(Artifact::App) || + artifact.is_a?(Artifact::Binary) || + (include_manual_installers && + artifact.is_a?(Artifact::Installer) && + artifact.manual_install && + [".app", ".pkg"].include?(artifact.path.extname.downcase)) + end + + if @artifacts_extracted && @tmpdir + yield artifacts, @tmpdir if block_given? + return + end + + return if artifacts.empty? + + @tmpdir ||= T.let(Pathname(Dir.mktmpdir("cask-audit", HOMEBREW_TEMP)), T.nilable(Pathname)) + + # Clean up tmp dir when @tmpdir object is destroyed + ObjectSpace.define_finalizer( + @tmpdir, + proc { FileUtils.remove_entry(@tmpdir) }, + ) + + ohai "Downloading and extracting artifacts" + + downloaded_path = download.fetch + + primary_container = UnpackStrategy.detect(downloaded_path, type: @cask.container&.type, merge_xattrs: true) + return if primary_container.nil? + + # If the container has any dependencies we need to install them or unpacking will fail. + if primary_container.dependencies.any? + + install_options = { + show_header: true, + installed_on_request: false, + verbose: false, + }.compact + + Homebrew::Install.perform_preinstall_checks_once + formula_installers = primary_container.dependencies.filter_map do |dep| + next unless dep.is_a?(Formula) + next if dep.linked? + + FormulaInstaller.new( + dep, + **install_options, + ) + end + valid_formula_installers = Homebrew::Install.fetch_formulae(formula_installers) + + formula_installers.each do |fi| + next unless valid_formula_installers.include?(fi) + + fi.install + fi.finish + end + end + + # Extract the container to the temporary directory. + primary_container.extract_nestedly(to: @tmpdir, basename: downloaded_path.basename, verbose: false) + + if (nested_container = @cask.container&.nested) + FileUtils.chmod_R "+rw", @tmpdir/nested_container, force: true, verbose: false + UnpackStrategy.detect(@tmpdir/nested_container, merge_xattrs: true) + .extract_nestedly(to: @tmpdir, verbose: false) + end + + # Propagate quarantine attributes from the downloaded file to extracted contents. + # This is necessary because some extraction tools (like 7zr) don't preserve xattrs. + if Quarantine.available? && Quarantine.detect(downloaded_path) + Quarantine.propagate(from: downloaded_path, to: @tmpdir) + end + + # Process rename operations after extraction + # Create a temporary installer to process renames in the audit directory + temp_installer = Installer.new(@cask) + temp_installer.process_rename_operations(target_dir: @tmpdir) + + # Set the flag to indicate that extraction has occurred. + @artifacts_extracted = T.let(true, T.nilable(TrueClass)) + + # Yield the artifacts and temp directory to the block if provided. + yield artifacts, @tmpdir if block_given? + end + + sig { params(min_os: T.nilable(T.any(String, MacOSVersion))).returns(T.nilable(MacOSVersion)) } + def normalize_min_os(min_os) + return if min_os.nil? + return if min_os.is_a?(String) && min_os.blank? + + min_os = if min_os.is_a?(MacOSVersion) + min_os.strip_patch + else + MacOSVersion.new(min_os).strip_patch + end + + # Big Sur is sometimes identified as 10.16, so we override it to the + # expected macOS version (11). + min_os = MacOSVersion.new("11") if min_os == "10.16" + + min_os + rescue MacOSVersion::Error + nil + end + private sig { void } @@ -562,107 +686,6 @@ module Cask end end - sig { - params( - include_manual_installers: T::Boolean, - _block: T.nilable(T.proc.params( - arg0: T::Array[T.any(Artifact::Installer, Artifact::Pkg, Artifact::Relocated)], - arg1: Pathname, - ).void), - ).void - } - def extract_artifacts(include_manual_installers: false, &_block) - return unless online? - return if (download = self.download).nil? - - artifacts = cask.artifacts.select do |artifact| - artifact.is_a?(Artifact::Pkg) || - artifact.is_a?(Artifact::App) || - artifact.is_a?(Artifact::Binary) || - (include_manual_installers && - artifact.is_a?(Artifact::Installer) && - artifact.manual_install && - [".app", ".pkg"].include?(artifact.path.extname.downcase)) - end - - if @artifacts_extracted && @tmpdir - yield artifacts, @tmpdir if block_given? - return - end - - return if artifacts.empty? - - @tmpdir ||= T.let(Pathname(Dir.mktmpdir("cask-audit", HOMEBREW_TEMP)), T.nilable(Pathname)) - - # Clean up tmp dir when @tmpdir object is destroyed - ObjectSpace.define_finalizer( - @tmpdir, - proc { FileUtils.remove_entry(@tmpdir) }, - ) - - ohai "Downloading and extracting artifacts" - - downloaded_path = download.fetch - - primary_container = UnpackStrategy.detect(downloaded_path, type: @cask.container&.type, merge_xattrs: true) - return if primary_container.nil? - - # If the container has any dependencies we need to install them or unpacking will fail. - if primary_container.dependencies.any? - - install_options = { - show_header: true, - installed_on_request: false, - verbose: false, - }.compact - - Homebrew::Install.perform_preinstall_checks_once - formula_installers = primary_container.dependencies.filter_map do |dep| - next unless dep.is_a?(Formula) - next if dep.linked? - - FormulaInstaller.new( - dep, - **install_options, - ) - end - valid_formula_installers = Homebrew::Install.fetch_formulae(formula_installers) - - formula_installers.each do |fi| - next unless valid_formula_installers.include?(fi) - - fi.install - fi.finish - end - end - - # Extract the container to the temporary directory. - primary_container.extract_nestedly(to: @tmpdir, basename: downloaded_path.basename, verbose: false) - - if (nested_container = @cask.container&.nested) - FileUtils.chmod_R "+rw", @tmpdir/nested_container, force: true, verbose: false - UnpackStrategy.detect(@tmpdir/nested_container, merge_xattrs: true) - .extract_nestedly(to: @tmpdir, verbose: false) - end - - # Propagate quarantine attributes from the downloaded file to extracted contents. - # This is necessary because some extraction tools (like 7zr) don't preserve xattrs. - if Quarantine.available? && Quarantine.detect(downloaded_path) - Quarantine.propagate(from: downloaded_path, to: @tmpdir) - end - - # Process rename operations after extraction - # Create a temporary installer to process renames in the audit directory - temp_installer = Installer.new(@cask) - temp_installer.process_rename_operations(target_dir: @tmpdir) - - # Set the flag to indicate that extraction has occurred. - @artifacts_extracted = T.let(true, T.nilable(TrueClass)) - - # Yield the artifacts and temp directory to the block if provided. - yield artifacts, @tmpdir if block_given? - end - sig { void } def audit_rosetta return if (url = cask.url).nil? @@ -955,26 +978,6 @@ module Cask normalize_min_os(min_os) end - sig { params(min_os: T.nilable(T.any(String, MacOSVersion))).returns(T.nilable(MacOSVersion)) } - def normalize_min_os(min_os) - return if min_os.nil? - return if min_os.is_a?(String) && min_os.blank? - - min_os = if min_os.is_a?(MacOSVersion) - min_os.strip_patch - else - MacOSVersion.new(min_os).strip_patch - end - - # Big Sur is sometimes identified as 10.16, so we override it to the - # expected macOS version (11). - min_os = MacOSVersion.new("11") if min_os == "10.16" - - min_os - rescue MacOSVersion::Error - nil - end - sig { params(path: Pathname).returns(T.nilable(String)) } def get_plist_main_binary(path) return unless online? diff --git a/Library/Homebrew/cask/auditor.rb b/Library/Homebrew/cask/auditor.rb index 19cad7680e..13edfe2cbb 100644 --- a/Library/Homebrew/cask/auditor.rb +++ b/Library/Homebrew/cask/auditor.rb @@ -102,8 +102,6 @@ module Cask errors end - private - sig { params(audit: T.nilable(Audit)).returns(T::Boolean) } def output_summary?(audit = nil) return true if @any_named_args @@ -113,6 +111,8 @@ module Cask audit.errors? end + private + sig { params(languages: T::Array[String]).returns(::Cask::Audit) } def audit_languages(languages) original_config = cask.config diff --git a/Library/Homebrew/cask/cask_loader.rb b/Library/Homebrew/cask/cask_loader.rb index 19de48c119..bb2466e694 100644 --- a/Library/Homebrew/cask/cask_loader.rb +++ b/Library/Homebrew/cask/cask_loader.rb @@ -147,6 +147,9 @@ module Cask sig { returns(Pathname) } attr_reader :path + sig { params(from_installed_caskfile: T::Boolean).void } + attr_writer :from_installed_caskfile + sig { params(path: T.any(Pathname, String), token: String).void } def initialize(path, token: T.unsafe(nil)) super() diff --git a/Library/Homebrew/cask/caskroom.rb b/Library/Homebrew/cask/caskroom.rb index 984168683e..068f7eba96 100644 --- a/Library/Homebrew/cask/caskroom.rb +++ b/Library/Homebrew/cask/caskroom.rb @@ -205,6 +205,13 @@ module Cask path.stat.gid == group.gid end + @expected_caskroom_group = T.let(nil, T.nilable(String)) + + class << self + sig { params(expected_caskroom_group: T.nilable(String)).void } + attr_writer :expected_caskroom_group + end + sig { returns(String) } def self.expected_caskroom_group "admin" diff --git a/Library/Homebrew/cask/download.rb b/Library/Homebrew/cask/download.rb index 60fc616a70..24a716e0a9 100644 --- a/Library/Homebrew/cask/download.rb +++ b/Library/Homebrew/cask/download.rb @@ -229,6 +229,9 @@ module Cask sig { override.returns(String) } def download_queue_type = "Cask" + sig { override.returns(String) } + def download_name = cask.token + private sig { void } @@ -280,8 +283,5 @@ module Cask def cache Cache.path end - - sig { override.returns(String) } - def download_name = cask.token end end diff --git a/Library/Homebrew/cli/parser.rb b/Library/Homebrew/cli/parser.rb index 3c386fadb9..c81c9e0ca6 100644 --- a/Library/Homebrew/cli/parser.rb +++ b/Library/Homebrew/cli/parser.rb @@ -56,6 +56,9 @@ module Homebrew sig { returns(T::Array[Subcommand]) } attr_reader :subcommands + sig { returns(T.nilable(Integer)) } + attr_reader :min_named_args + sig { params(cmd_path: Pathname).returns(T.nilable(CLI::Parser)) } def self.from_cmd_path(cmd_path) cmd_args_method_name = Commands.args_method_name(cmd_path) diff --git a/Library/Homebrew/cmd/fetch.rb b/Library/Homebrew/cmd/fetch.rb index 25f38913e8..a436e0dcd1 100644 --- a/Library/Homebrew/cmd/fetch.rb +++ b/Library/Homebrew/cmd/fetch.rb @@ -172,6 +172,68 @@ module Homebrew download_queue.shutdown end + sig { params(cask: Cask::Cask).returns(T::Array[Cask::Download]) } + def cask_downloads(cask) + ref = cask.reloadable_ref + + if args.all_platforms? && cask.loaded_from_api? + opoo "Cask #{cask} was loaded from the API; cannot fetch all operating system and " \ + "architecture variants. Set `HOMEBREW_NO_INSTALL_FROM_API=1` to fetch them all." + end + + # With `--all-platforms`, a cask without `on_system` blocks resolves + # identically everywhere, so one combination covers the whole matrix. + cask_combinations = args.os_arch_combinations + cask_combinations = cask_combinations.first(1) if args.all_platforms? && !cask.on_system_blocks_exist? + + downloads = T.let([], T::Array[Cask::Download]) + enqueued_urls = Set.new + + cask_combinations.each do |os, arch| + SimulateSystem.with(os:, arch:) do + loaded_cask = begin + Cask::CaskLoader.load(ref) + rescue Cask::CaskInvalidError, Cask::CaskUnreadableError + raise unless cask.on_system_blocks_exist? + end + if loaded_cask.nil? || loaded_cask.depends_on.arch&.none? { |dep_arch| dep_arch[:type] == arch } + opoo "Cask #{cask} is not supported on os #{os} and arch #{arch}" + next + end + + languages = (loaded_cask.languages if args.all_platforms?) + languages = [nil] if languages.blank? + + languages.each do |language| + localized_cask = loaded_cask + if language + # Reload per language: `Cask::Download` reads `sha256`/`url` + # lazily, so each download needs its own cask instance. + localized_cask = Cask::CaskLoader.load(ref) + localized_cask.config = localized_cask.config.merge( + Cask::Config.new(explicit: { languages: [language] }), + ) + end + + if localized_cask.url.nil? || localized_cask.sha256.nil? + opoo "Cask #{cask} is not supported on os #{os} and arch #{arch}" + next + end + + next unless enqueued_urls.add?(localized_cask.url.to_s) + + downloads << Cask::Download.new( + localized_cask, + quarantine: true, + require_sha: Homebrew::EnvConfig.cask_opts_require_sha?, + ) + end + end + end + + downloads + end + private sig { returns(T::Boolean) } @@ -288,68 +350,6 @@ module Homebrew names end - sig { params(cask: Cask::Cask).returns(T::Array[Cask::Download]) } - def cask_downloads(cask) - ref = cask.reloadable_ref - - if args.all_platforms? && cask.loaded_from_api? - opoo "Cask #{cask} was loaded from the API; cannot fetch all operating system and " \ - "architecture variants. Set `HOMEBREW_NO_INSTALL_FROM_API=1` to fetch them all." - end - - # With `--all-platforms`, a cask without `on_system` blocks resolves - # identically everywhere, so one combination covers the whole matrix. - cask_combinations = args.os_arch_combinations - cask_combinations = cask_combinations.first(1) if args.all_platforms? && !cask.on_system_blocks_exist? - - downloads = T.let([], T::Array[Cask::Download]) - enqueued_urls = Set.new - - cask_combinations.each do |os, arch| - SimulateSystem.with(os:, arch:) do - loaded_cask = begin - Cask::CaskLoader.load(ref) - rescue Cask::CaskInvalidError, Cask::CaskUnreadableError - raise unless cask.on_system_blocks_exist? - end - if loaded_cask.nil? || loaded_cask.depends_on.arch&.none? { |dep_arch| dep_arch[:type] == arch } - opoo "Cask #{cask} is not supported on os #{os} and arch #{arch}" - next - end - - languages = (loaded_cask.languages if args.all_platforms?) - languages = [nil] if languages.blank? - - languages.each do |language| - localized_cask = loaded_cask - if language - # Reload per language: `Cask::Download` reads `sha256`/`url` - # lazily, so each download needs its own cask instance. - localized_cask = Cask::CaskLoader.load(ref) - localized_cask.config = localized_cask.config.merge( - Cask::Config.new(explicit: { languages: [language] }), - ) - end - - if localized_cask.url.nil? || localized_cask.sha256.nil? - opoo "Cask #{cask} is not supported on os #{os} and arch #{arch}" - next - end - - next unless enqueued_urls.add?(localized_cask.url.to_s) - - downloads << Cask::Download.new( - localized_cask, - quarantine: true, - require_sha: Homebrew::EnvConfig.cask_opts_require_sha?, - ) - end - end - end - - downloads - end - sig { returns(Integer) } def retries @retries ||= T.let(args.retry? ? FETCH_MAX_TRIES : 1, T.nilable(Integer)) diff --git a/Library/Homebrew/cmd/info.rb b/Library/Homebrew/cmd/info.rb index f586513df1..a707075ed8 100644 --- a/Library/Homebrew/cmd/info.rb +++ b/Library/Homebrew/cmd/info.rb @@ -349,39 +349,6 @@ module Homebrew private_class_method :formula_metadata_lines, :formatted_time, :pin_path_mtime, :formula_installs_from_source?, :cask_requirements_lines - private - - sig { void } - def print_statistics - return unless HOMEBREW_CELLAR.exist? - - count = Formula.racks.length - puts "#{Utils.pluralize("keg", count, include_count: true)}, #{HOMEBREW_CELLAR.dup.abv}" - end - - sig { void } - def print_analytics - if args.no_named? - Utils::Analytics.output(args:) - return - end - - args.named.to_formulae_and_casks_and_unavailable.each_with_index do |obj, i| - puts unless i.zero? - - case obj - when Formula - Utils::Analytics.formula_output(obj, args:) if obj.core_formula? - when Cask::Cask - Utils::Analytics.cask_output(obj, args:) if obj.tap&.core_cask_tap? - when FormulaOrCaskUnavailableError - Utils::Analytics.output(filter: obj.name, args:) - else - raise - end - end - end - sig { params(quiet: T::Boolean).void } def print_info(quiet: false) objects = args.named.to_formulae_and_casks_and_unavailable(uniq: false) @@ -411,25 +378,6 @@ module Homebrew end end - sig { - params(resolved: T::Array[[T.untyped, T.nilable(Tap)]]).returns(T::Array[[T.untyped, T.nilable(Tap)]]) - } - def unique_by_display_name(resolved) - resolved.uniq do |obj, _shadowed_by| - case obj - when Formula, Cask::Cask then obj.full_name - else obj - end - end - end - - sig { params(formula: Formula, user_qualified: T::Boolean).returns([Formula, T.nilable(Tap)]) } - def display_resolution(formula, user_qualified:) - return [formula, nil] if user_qualified - - installed_resolution(formula) - end - sig { params(formula_or_cask: T.any(Formula, Cask::Cask), qualified_inputs: T::Set[String]).returns(T::Boolean) } def formula_qualified_by_user?(formula_or_cask, qualified_inputs) return false if qualified_inputs.empty? @@ -441,24 +389,6 @@ module Homebrew names.any? { |n| qualified_inputs.include?(n) } end - sig { params(formula_or_cask: T.any(Formula, Cask::Cask), quiet: T::Boolean, shadowed_by: T.nilable(Tap)).void } - def info_formula_or_cask(formula_or_cask, quiet:, shadowed_by: nil) - case formula_or_cask - when Formula - if quiet - info_formula_summary(formula_or_cask) - else - info_formula(formula_or_cask, shadowed_by:) - end - when Cask::Cask - if quiet - info_cask_summary(formula_or_cask) - else - info_cask(formula_or_cask) - end - end - end - sig { params(formula: Formula).returns([Formula, T.nilable(Tap)]) } def installed_resolution(formula) keg = formula.installed_kegs.last @@ -472,95 +402,6 @@ module Homebrew [formula, nil] end - sig { params(formula: Formula).returns(T.nilable(Formula)) } - def shadowing_installed_formula(formula) - installed_formula, shadowed_by = installed_resolution(formula) - installed_formula if shadowed_by - end - - sig { params(formula: Formula, qualified_inputs: T::Set[String]).returns(Formula) } - def swap_to_installed_formula(formula, qualified_inputs) - return formula if formula_qualified_by_user?(formula, qualified_inputs) - - installed_resolution(formula).first - end - - sig { params(version: T.any(T::Boolean, String)).returns(Symbol) } - def json_version(version) - version_hash = { - true => :default, - "v1" => :v1, - "v2" => :v2, - } - - raise UsageError, "invalid JSON version: #{version}" unless version_hash.include?(version) - - version_hash[version] - end - - sig { params(json: T.any(T::Boolean, String), eval_all: T::Boolean).void } - def print_json(json, eval_all) - raise FormulaOrCaskUnspecifiedError if !(eval_all || args.installed?) && args.no_named? - - qualified_inputs = args.named.select { |name| name.include?("/") }.to_set - - json = case json_version(json) - when :v1, :default - raise UsageError, "Cannot specify `--cask` when using `--json=v1`!" if args.cask? - - formulae = if eval_all - Formula.all(eval_all:).sort - elsif args.installed? - Formula.installed.sort - else - args.named.to_formulae.map { |f| swap_to_installed_formula(f, qualified_inputs) } - end - - if args.variations? - formulae.map(&:to_hash_with_variations) - else - formulae.map(&:to_hash) - end - when :v2 - formulae, casks = T.let( - if eval_all - formulae = [] if args.cask? - formulae ||= Formula.all(eval_all:).sort - casks = [] if args.formula? - casks ||= Cask::Cask.all(eval_all:).sort_by(&:full_name) - [formulae, casks] - elsif args.installed? - formulae = [] if args.cask? - formulae ||= Formula.installed.sort - casks = [] if args.formula? - casks ||= Cask::Caskroom.casks.sort_by(&:full_name) - [formulae, casks] - else - named_formulae, named_casks = T.cast( - args.named.to_formulae_to_casks, [T::Array[Formula], T::Array[Cask::Cask]] - ) - [named_formulae.map { |f| swap_to_installed_formula(f, qualified_inputs) }, named_casks] - end, [T::Array[Formula], T::Array[Cask::Cask]] - ) - - if args.variations? - { - "formulae" => formulae.map(&:to_hash_with_variations), - "casks" => casks.map(&:to_hash_with_variations), - } - else - { - "formulae" => formulae.map(&:to_hash), - "casks" => casks.map(&:to_h), - } - end - else - raise - end - - puts JSON.pretty_generate(json) - end - sig { params(formula_or_cask: T.any(Formula, Cask::Cask)).returns(String) } def github_info(formula_or_cask) tap = T.let(nil, T.nilable(Tap)) @@ -594,13 +435,6 @@ module Homebrew github_remote_path(remote, path.to_s) end - sig { params(name: String, description: T.nilable(String), installed: T::Boolean).returns(String) } - def info_summary_title(name, description, installed:) - name = pretty_installed(name) if installed - - "#{name}#{": #{description}" if description.present?}" - end - sig { params(formula: Formula).void } def info_formula_summary(formula) kegs = formula.installed_kegs @@ -830,6 +664,172 @@ module Homebrew Utils::Analytics.formula_output(formula, args:) end + private + + sig { void } + def print_statistics + return unless HOMEBREW_CELLAR.exist? + + count = Formula.racks.length + puts "#{Utils.pluralize("keg", count, include_count: true)}, #{HOMEBREW_CELLAR.dup.abv}" + end + + sig { void } + def print_analytics + if args.no_named? + Utils::Analytics.output(args:) + return + end + + args.named.to_formulae_and_casks_and_unavailable.each_with_index do |obj, i| + puts unless i.zero? + + case obj + when Formula + Utils::Analytics.formula_output(obj, args:) if obj.core_formula? + when Cask::Cask + Utils::Analytics.cask_output(obj, args:) if obj.tap&.core_cask_tap? + when FormulaOrCaskUnavailableError + Utils::Analytics.output(filter: obj.name, args:) + else + raise + end + end + end + + sig { + params(resolved: T::Array[[T.untyped, T.nilable(Tap)]]).returns(T::Array[[T.untyped, T.nilable(Tap)]]) + } + def unique_by_display_name(resolved) + resolved.uniq do |obj, _shadowed_by| + case obj + when Formula, Cask::Cask then obj.full_name + else obj + end + end + end + + sig { params(formula: Formula, user_qualified: T::Boolean).returns([Formula, T.nilable(Tap)]) } + def display_resolution(formula, user_qualified:) + return [formula, nil] if user_qualified + + installed_resolution(formula) + end + + sig { params(formula_or_cask: T.any(Formula, Cask::Cask), quiet: T::Boolean, shadowed_by: T.nilable(Tap)).void } + def info_formula_or_cask(formula_or_cask, quiet:, shadowed_by: nil) + case formula_or_cask + when Formula + if quiet + info_formula_summary(formula_or_cask) + else + info_formula(formula_or_cask, shadowed_by:) + end + when Cask::Cask + if quiet + info_cask_summary(formula_or_cask) + else + info_cask(formula_or_cask) + end + end + end + + sig { params(formula: Formula).returns(T.nilable(Formula)) } + def shadowing_installed_formula(formula) + installed_formula, shadowed_by = installed_resolution(formula) + installed_formula if shadowed_by + end + + sig { params(formula: Formula, qualified_inputs: T::Set[String]).returns(Formula) } + def swap_to_installed_formula(formula, qualified_inputs) + return formula if formula_qualified_by_user?(formula, qualified_inputs) + + installed_resolution(formula).first + end + + sig { params(version: T.any(T::Boolean, String)).returns(Symbol) } + def json_version(version) + version_hash = { + true => :default, + "v1" => :v1, + "v2" => :v2, + } + + raise UsageError, "invalid JSON version: #{version}" unless version_hash.include?(version) + + version_hash[version] + end + + sig { params(json: T.any(T::Boolean, String), eval_all: T::Boolean).void } + def print_json(json, eval_all) + raise FormulaOrCaskUnspecifiedError if !(eval_all || args.installed?) && args.no_named? + + qualified_inputs = args.named.select { |name| name.include?("/") }.to_set + + json = case json_version(json) + when :v1, :default + raise UsageError, "Cannot specify `--cask` when using `--json=v1`!" if args.cask? + + formulae = if eval_all + Formula.all(eval_all:).sort + elsif args.installed? + Formula.installed.sort + else + args.named.to_formulae.map { |f| swap_to_installed_formula(f, qualified_inputs) } + end + + if args.variations? + formulae.map(&:to_hash_with_variations) + else + formulae.map(&:to_hash) + end + when :v2 + formulae, casks = T.let( + if eval_all + formulae = [] if args.cask? + formulae ||= Formula.all(eval_all:).sort + casks = [] if args.formula? + casks ||= Cask::Cask.all(eval_all:).sort_by(&:full_name) + [formulae, casks] + elsif args.installed? + formulae = [] if args.cask? + formulae ||= Formula.installed.sort + casks = [] if args.formula? + casks ||= Cask::Caskroom.casks.sort_by(&:full_name) + [formulae, casks] + else + named_formulae, named_casks = T.cast( + args.named.to_formulae_to_casks, [T::Array[Formula], T::Array[Cask::Cask]] + ) + [named_formulae.map { |f| swap_to_installed_formula(f, qualified_inputs) }, named_casks] + end, [T::Array[Formula], T::Array[Cask::Cask]] + ) + + if args.variations? + { + "formulae" => formulae.map(&:to_hash_with_variations), + "casks" => casks.map(&:to_hash_with_variations), + } + else + { + "formulae" => formulae.map(&:to_hash), + "casks" => casks.map(&:to_h), + } + end + else + raise + end + + puts JSON.pretty_generate(json) + end + + sig { params(name: String, description: T.nilable(String), installed: T::Boolean).returns(String) } + def info_summary_title(name, description, installed:) + name = pretty_installed(name) if installed + + "#{name}#{": #{description}" if description.present?}" + end + sig { params(formula: Formula, verbose: T::Boolean).returns(T::Array[String]) } def installed_section_lines(formula, verbose: false) siblings = formula.versioned_formulae diff --git a/Library/Homebrew/cmd/outdated.rb b/Library/Homebrew/cmd/outdated.rb index c3f0daaa70..425ecdad8f 100644 --- a/Library/Homebrew/cmd/outdated.rb +++ b/Library/Homebrew/cmd/outdated.rb @@ -90,6 +90,31 @@ module Homebrew Homebrew.failed = args.named.present? && outdated.present? end + sig { + params(formulae_or_casks: T::Array[T.any(Formula, Cask::Cask)]).returns(T::Array[T.any(Formula, Cask::Cask)]) + } + def select_outdated(formulae_or_casks) + formulae_or_casks.select do |formula_or_cask| + if formula_or_cask.is_a?(Formula) + if minimum_version.present? + formula_outdated_kegs(formula_or_cask).present? + else + formula_or_cask.outdated?(fetch_head: args.fetch_HEAD?) + end + else + if minimum_version.present? + next MinimumVersion.cask_installed_below?(formula_or_cask, T.must(minimum_version)) + end + + cask_greedy = upgrade_greedy_cask?(args.greedy?, formula_or_cask) + + formula_or_cask.outdated?(greedy: cask_greedy, + greedy_latest: args.greedy_latest?, + greedy_auto_updates: args.greedy_auto_updates?) + end + end + end + private sig { params(formulae_or_casks: T::Array[T.any(Formula, Cask::Cask)]).void } @@ -243,31 +268,6 @@ module Homebrew [select_outdated(formulae).sort, select_outdated(casks)] end - sig { - params(formulae_or_casks: T::Array[T.any(Formula, Cask::Cask)]).returns(T::Array[T.any(Formula, Cask::Cask)]) - } - def select_outdated(formulae_or_casks) - formulae_or_casks.select do |formula_or_cask| - if formula_or_cask.is_a?(Formula) - if minimum_version.present? - formula_outdated_kegs(formula_or_cask).present? - else - formula_or_cask.outdated?(fetch_head: args.fetch_HEAD?) - end - else - if minimum_version.present? - next MinimumVersion.cask_installed_below?(formula_or_cask, T.must(minimum_version)) - end - - cask_greedy = upgrade_greedy_cask?(args.greedy?, formula_or_cask) - - formula_or_cask.outdated?(greedy: cask_greedy, - greedy_latest: args.greedy_latest?, - greedy_auto_updates: args.greedy_auto_updates?) - end - end - end - sig { params(formula: Formula).returns(T::Array[Keg]) } def formula_outdated_kegs(formula) MinimumVersion.formula_outdated_kegs(formula, minimum_version, fetch_head: args.fetch_HEAD?) diff --git a/Library/Homebrew/cmd/search.rb b/Library/Homebrew/cmd/search.rb index 7cccbbe5ce..a6531b6352 100644 --- a/Library/Homebrew/cmd/search.rb +++ b/Library/Homebrew/cmd/search.rb @@ -90,6 +90,21 @@ module Homebrew print_regex_help end + sig { params(query: String, found_matches: T::Boolean).void } + def print_missing_formula_help(query, found_matches) + return unless $stdout.tty? + return if query.match?(Search::QUERY_REGEX) + + reason = MissingFormula.reason(query, silent: true) + return if reason.nil? + + if found_matches + puts + puts "If you meant #{query.inspect} specifically:" + end + puts reason + end + private sig { void } @@ -154,21 +169,6 @@ module Homebrew odie "No formulae or casks found for #{query.inspect}." if count.zero? end - - sig { params(query: String, found_matches: T::Boolean).void } - def print_missing_formula_help(query, found_matches) - return unless $stdout.tty? - return if query.match?(Search::QUERY_REGEX) - - reason = MissingFormula.reason(query, silent: true) - return if reason.nil? - - if found_matches - puts - puts "If you meant #{query.inspect} specifically:" - end - puts reason - end end end end diff --git a/Library/Homebrew/cmd/source.rb b/Library/Homebrew/cmd/source.rb index 6900c32c20..28a7d28593 100644 --- a/Library/Homebrew/cmd/source.rb +++ b/Library/Homebrew/cmd/source.rb @@ -45,26 +45,6 @@ module Homebrew exec_browser(*repo_urls) end - private - - sig { params(formula: Formula).returns(T.nilable(String)) } - def extract_repo_url(formula) - urls_to_check = [ - formula.head&.url, - formula.stable&.url, - formula.homepage, - ] - - urls_to_check.each do |url| - next if url.nil? - - repo_url = url_to_repo(url) - return repo_url if repo_url - end - - nil - end - sig { params(url: String).returns(T.nilable(String)) } def url_to_repo(url) github_repo_url(url) || @@ -207,6 +187,26 @@ module Homebrew rescue JSON::ParserError nil end + + private + + sig { params(formula: Formula).returns(T.nilable(String)) } + def extract_repo_url(formula) + urls_to_check = [ + formula.head&.url, + formula.stable&.url, + formula.homepage, + ] + + urls_to_check.each do |url| + next if url.nil? + + repo_url = url_to_repo(url) + return repo_url if repo_url + end + + nil + end end end end diff --git a/Library/Homebrew/cmd/tap-info.rb b/Library/Homebrew/cmd/tap-info.rb index 8ef37415cb..ef1988d049 100644 --- a/Library/Homebrew/cmd/tap-info.rb +++ b/Library/Homebrew/cmd/tap-info.rb @@ -40,8 +40,6 @@ module Homebrew end end - private - sig { params(taps: T::Array[Tap]).void } def print_tap_info(taps) if taps.none? @@ -95,9 +93,6 @@ module Homebrew end end - LISTING_LIMIT = 30 - private_constant :LISTING_LIMIT - sig { params(tap: Tap).void } def print_tap_listings(tap) commands = tap.command_files @@ -124,32 +119,6 @@ module Homebrew end end - sig { - params( - tap: Tap, - label: String, - all: T::Array[String], - installed: T::Array[String], - min_width: Integer, - block: T.proc.params(name: String).returns(String), - ).void - } - def print_section(tap, label, all, installed, min_width:, &block) - return if all.none? - - if all.size <= LISTING_LIMIT - ohai label, Formatter.columns(all.map(&block), min_width:) - elsif installed.any? - ohai label - opoo "Tap has more than #{LISTING_LIMIT} #{label.downcase}; showing only installed entries." - puts Formatter.columns(installed.map(&block), min_width:) - else - ohai label - opoo "Tap has more than #{LISTING_LIMIT} #{label.downcase} and none are installed." - puts "See: #{tap.remote}" if tap.remote.present? - end - end - sig { params(tap: Tap, name: String, installed: T::Boolean).returns(String) } def decorate_formula(tap, name, installed:) formula = Formulary.factory("#{tap.name}/#{name}") @@ -187,6 +156,37 @@ module Homebrew puts JSON.pretty_generate(hashes) end + + private + + LISTING_LIMIT = 30 + private_constant :LISTING_LIMIT + + sig { + params( + tap: Tap, + label: String, + all: T::Array[String], + installed: T::Array[String], + min_width: Integer, + block: T.proc.params(name: String).returns(String), + ).void + } + def print_section(tap, label, all, installed, min_width:, &block) + return if all.none? + + if all.size <= LISTING_LIMIT + ohai label, Formatter.columns(all.map(&block), min_width:) + elsif installed.any? + ohai label + opoo "Tap has more than #{LISTING_LIMIT} #{label.downcase}; showing only installed entries." + puts Formatter.columns(installed.map(&block), min_width:) + else + ohai label + opoo "Tap has more than #{LISTING_LIMIT} #{label.downcase} and none are installed." + puts "See: #{tap.remote}" if tap.remote.present? + end + end end end end diff --git a/Library/Homebrew/cmd/update-report.rb b/Library/Homebrew/cmd/update-report.rb index 9827459da1..1b913db63a 100644 --- a/Library/Homebrew/cmd/update-report.rb +++ b/Library/Homebrew/cmd/update-report.rb @@ -50,6 +50,17 @@ module Homebrew end end + sig { void } + def donation_message + return if Settings.read("donationmessage") == "true" + + ohai "Homebrew is run entirely by unpaid volunteers. Please consider donating:" + puts " #{Formatter.url("https://github.com/Homebrew/brew#-donations")}\n\n" + + # Consider the message possibly missed if not a TTY. + Settings.write "donationmessage", true if $stdout.tty? + end + private sig { void } @@ -405,17 +416,6 @@ module Homebrew Utils::Analytics.messages_displayed! if $stdout.tty? end - sig { void } - def donation_message - return if Settings.read("donationmessage") == "true" - - ohai "Homebrew is run entirely by unpaid volunteers. Please consider donating:" - puts " #{Formatter.url("https://github.com/Homebrew/brew#-donations")}\n\n" - - # Consider the message possibly missed if not a TTY. - Settings.write "donationmessage", true if $stdout.tty? - end - sig { void } def install_from_api_message return if Settings.read("installfromapimessage") == "true" diff --git a/Library/Homebrew/cmd/update_report/reporter.rb b/Library/Homebrew/cmd/update_report/reporter.rb index cc255daec8..5ac3aa31e8 100644 --- a/Library/Homebrew/cmd/update_report/reporter.rb +++ b/Library/Homebrew/cmd/update_report/reporter.rb @@ -338,8 +338,6 @@ class Reporter end end - private - sig { params(name: String, new_name: String, new_tap: Tap).returns(T::Boolean) } def ensure_trusted_tap_installed!(name, new_name, new_tap) return true if new_tap.installed? @@ -368,33 +366,6 @@ class Reporter true end - sig { returns(Tap) } - attr_reader :tap - - sig { returns(String) } - attr_reader :initial_revision - - sig { returns(String) } - attr_reader :current_revision - - sig { returns(T.nilable(Pathname)) } - attr_reader :api_names_txt - - sig { returns(T.nilable(Pathname)) } - attr_reader :api_names_before_txt - - sig { returns(T.nilable(Pathname)) } - attr_reader :api_dir_prefix - - sig { - params(api_names_txt: T.nilable(Pathname), api_names_before_txt: T.nilable(Pathname), - api_dir_prefix: T.nilable(Pathname)).returns(T::Boolean) - } - def installed_from_api?(api_names_txt = @api_names_txt, api_names_before_txt = @api_names_before_txt, - api_dir_prefix = @api_dir_prefix) - !api_names_txt.nil? && !api_names_before_txt.nil? && !api_dir_prefix.nil? - end - sig { returns(String) } def diff @diff ||= T.let(nil, T.nilable(String)) @@ -436,4 +407,33 @@ class Reporter ) end end + + private + + sig { returns(Tap) } + attr_reader :tap + + sig { returns(String) } + attr_reader :initial_revision + + sig { returns(String) } + attr_reader :current_revision + + sig { returns(T.nilable(Pathname)) } + attr_reader :api_names_txt + + sig { returns(T.nilable(Pathname)) } + attr_reader :api_names_before_txt + + sig { returns(T.nilable(Pathname)) } + attr_reader :api_dir_prefix + + sig { + params(api_names_txt: T.nilable(Pathname), api_names_before_txt: T.nilable(Pathname), + api_dir_prefix: T.nilable(Pathname)).returns(T::Boolean) + } + def installed_from_api?(api_names_txt = @api_names_txt, api_names_before_txt = @api_names_before_txt, + api_dir_prefix = @api_dir_prefix) + !api_names_txt.nil? && !api_names_before_txt.nil? && !api_dir_prefix.nil? + end end diff --git a/Library/Homebrew/cmd/update_report/reporter_hub.rb b/Library/Homebrew/cmd/update_report/reporter_hub.rb index 263e852d96..66b84fa573 100644 --- a/Library/Homebrew/cmd/update_report/reporter_hub.rb +++ b/Library/Homebrew/cmd/update_report/reporter_hub.rb @@ -20,6 +20,11 @@ class ReporterHub T.cast(@hash.fetch(key, []), T::Array[String]) end + sig { returns(T::Array[[String, String]]) } + def renamed_formulae + T.cast(@hash.fetch(:R, []), T::Array[[String, String]]) + end + sig { params(reporter: Reporter, auto_update: T::Boolean).void } def add(reporter, auto_update: false) @reporters << reporter diff --git a/Library/Homebrew/cmd/upgrade.rb b/Library/Homebrew/cmd/upgrade.rb index 2f07b1a74a..4162be29d8 100644 --- a/Library/Homebrew/cmd/upgrade.rb +++ b/Library/Homebrew/cmd/upgrade.rb @@ -336,51 +336,6 @@ module Homebrew show_final_upgrade_summary end - private - - sig { returns(T.nilable(String)) } - def minimum_version = args.minimum_version || args.min_version - - sig { params(formula: Formula).returns(T::Boolean) } - def formula_outdated?(formula) - outdated = formula.outdated?(fetch_head: args.fetch_HEAD?) - return false if outdated && fetched_head_formula_current?(formula) - - version = minimum_version - return outdated if version.blank? - - outdated && MinimumVersion.formula_outdated_kegs(formula, version, fetch_head: args.fetch_HEAD?).present? - end - - sig { params(formula: Formula).returns(T::Boolean) } - def fetched_head_formula_current?(formula) - return false unless args.fetch_HEAD? - return false unless formula.head? - return false unless formula.optlinked? - - old_version = Keg.new(formula.opt_prefix).version - return false unless old_version.head? - - formula.latest_head_pkg_version(fetch_head: true).to_s == old_version.to_s - end - - sig { params(casks: T::Array[Cask::Cask], quiet: T::Boolean).returns(T::Array[Cask::Cask]) } - def minimum_version_casks(casks, quiet: args.quiet?) - version = minimum_version - return casks if version.blank? - - casks.select do |cask| - if MinimumVersion.cask_installed_below?(cask, version) - true - else - unless quiet - opoo "Not upgrading #{cask.token}, the installed version is not below the minimum version #{version}" - end - false - end - end - end - sig { params(formulae: T::Array[Formula], show_upgrade_summary: T::Boolean, dry_run: T::Boolean).returns(T.nilable(FormulaeUpgradeContext)) @@ -624,15 +579,6 @@ module Homebrew end end - sig { params(title: String, items: T::Array[String]).void } - def show_final_upgrade_summary_section(title, items) - items = items.uniq - return if items.empty? - - oh1 title - puts items.join("\n") - end - sig { params(formulae: T::Array[Formula], include_sizes: T::Boolean).returns(T::Array[String]) } def formula_upgrade_descriptions(formulae, include_sizes: false) formulae.map do |formula| @@ -654,31 +600,6 @@ module Homebrew end end - sig { params(formula: Formula, old_version: PkgVersion).returns(String) } - def formula_upgrade_display_version(formula, old_version) - return formula.pkg_version.to_s if !old_version.head? || !formula.head? - return formula.pkg_version.to_s if formula.pkg_version.to_s != old_version.to_s - return "latest HEAD" unless args.fetch_HEAD? - - latest_head_version = formula.latest_head_pkg_version(fetch_head: true) - return "latest HEAD" if latest_head_version.to_s == old_version.to_s - - latest_head_version.to_s - end - - sig { params(formula: Formula).returns(String) } - def formula_upgrade_size(formula) - return "" if args.build_from_source_formulae.include?(formula.name) - - bottle = formula.bottle - return "" unless bottle - - bottle.fetch_tab(quiet: !args.debug?) - return "" unless (download_size = bottle.bottle_size) - - " (#{Formatter.disk_usage_readable(download_size.to_i)})" - end - sig { params( formulae: T::Array[Formula], @@ -919,6 +840,85 @@ module Homebrew ofail e false end + + private + + sig { returns(T.nilable(String)) } + def minimum_version = args.minimum_version || args.min_version + + sig { params(formula: Formula).returns(T::Boolean) } + def formula_outdated?(formula) + outdated = formula.outdated?(fetch_head: args.fetch_HEAD?) + return false if outdated && fetched_head_formula_current?(formula) + + version = minimum_version + return outdated if version.blank? + + outdated && MinimumVersion.formula_outdated_kegs(formula, version, fetch_head: args.fetch_HEAD?).present? + end + + sig { params(formula: Formula).returns(T::Boolean) } + def fetched_head_formula_current?(formula) + return false unless args.fetch_HEAD? + return false unless formula.head? + return false unless formula.optlinked? + + old_version = Keg.new(formula.opt_prefix).version + return false unless old_version.head? + + formula.latest_head_pkg_version(fetch_head: true).to_s == old_version.to_s + end + + sig { params(casks: T::Array[Cask::Cask], quiet: T::Boolean).returns(T::Array[Cask::Cask]) } + def minimum_version_casks(casks, quiet: args.quiet?) + version = minimum_version + return casks if version.blank? + + casks.select do |cask| + if MinimumVersion.cask_installed_below?(cask, version) + true + else + unless quiet + opoo "Not upgrading #{cask.token}, the installed version is not below the minimum version #{version}" + end + false + end + end + end + + sig { params(title: String, items: T::Array[String]).void } + def show_final_upgrade_summary_section(title, items) + items = items.uniq + return if items.empty? + + oh1 title + puts items.join("\n") + end + + sig { params(formula: Formula, old_version: PkgVersion).returns(String) } + def formula_upgrade_display_version(formula, old_version) + return formula.pkg_version.to_s if !old_version.head? || !formula.head? + return formula.pkg_version.to_s if formula.pkg_version.to_s != old_version.to_s + return "latest HEAD" unless args.fetch_HEAD? + + latest_head_version = formula.latest_head_pkg_version(fetch_head: true) + return "latest HEAD" if latest_head_version.to_s == old_version.to_s + + latest_head_version.to_s + end + + sig { params(formula: Formula).returns(String) } + def formula_upgrade_size(formula) + return "" if args.build_from_source_formulae.include?(formula.name) + + bottle = formula.bottle + return "" unless bottle + + bottle.fetch_tab(quiet: !args.debug?) + return "" unless (download_size = bottle.bottle_size) + + " (#{Formatter.disk_usage_readable(download_size.to_i)})" + end end end end diff --git a/Library/Homebrew/dependency.rb b/Library/Homebrew/dependency.rb index 0adc409460..4f16c34809 100644 --- a/Library/Homebrew/dependency.rb +++ b/Library/Homebrew/dependency.rb @@ -163,6 +163,9 @@ class Dependency end class << self + sig { returns(T.nilable(T::Array[T.any(String, Symbol)])) } + attr_reader :expand_stack + # Expand the dependencies of each dependent recursively, optionally yielding # `[dependent, dep]` pairs to allow callers to apply arbitrary filters to # the list. diff --git a/Library/Homebrew/dev-cmd/bump-cask-pr.rb b/Library/Homebrew/dev-cmd/bump-cask-pr.rb index ff60c21780..7e6d0d933a 100644 --- a/Library/Homebrew/dev-cmd/bump-cask-pr.rb +++ b/Library/Homebrew/dev-cmd/bump-cask-pr.rb @@ -216,17 +216,6 @@ module Homebrew end end - private - - sig { params(version: Cask::DSL::Version, cask: Cask::Cask).returns(Cask::DSL::Version) } - def shortened_version(version, cask:) - if version.before_comma == cask.version.before_comma - version - else - version.before_comma - end - end - sig { params(cask: Cask::Cask, new_version: BumpVersionParser).returns(T::Array[[Symbol, Symbol]]) } def generate_system_options(cask, new_version) current_os = Homebrew::SimulateSystem.current_os @@ -375,6 +364,63 @@ module Homebrew contents end + sig { + params( + contents: String, + name: Symbol, + old_value: T.any(Numeric, String, Symbol), + new_value: T.any(Numeric, String, Symbol), + within: T.nilable(Symbol), + ).returns(String) + } + def replace_cask_stanza_value(contents, name, old_value, new_value, within: nil) + return contents if old_value == new_value + + cask_ast = Utils::AST::CaskAST.new(contents) + replacement_count = cask_ast.replace_stanza_value(name, old_value, new_value, within:) + if replacement_count.zero? + # Treat an already-applied replacement as a successful no-op so the + # per-(os, arch) loop in `replace_version_and_checksum` can yield the + # same general version more than once without raising. + return contents if cask_ast.replace_stanza_value(name, new_value, new_value, within:).positive? + + raise "Could not find '#{name}' stanza with value #{old_value.inspect}!" + end + + cask_ast.process + end + + sig { params(cask: Cask::Cask, new_version: BumpVersionParser).void } + def check_throttle(cask, new_version:) + return unless cask.tap + + throttle_rate = cask.livecheck.throttle + throttle_days = cask.livecheck.throttle_days + return if throttle_rate.nil? && throttle_days.nil? + + version = new_version.arm || new_version.intel || new_version.general + return unless version.is_a?(Cask::DSL::Version) + + return if Livecheck.throttle_allows_bump?(cask, version.to_s, throttle_rate:, throttle_days:) + + throttle_items = [] + throttle_items << "#{throttle_rate} releases on multiples of #{throttle_rate}" if throttle_rate + throttle_items << "#{throttle_days} #{Utils.pluralize("day", throttle_days)}" if throttle_days + + odie "#{cask.token} should only be updated every #{throttle_items.join(" or ")}" + end + + private + + sig { params(version: Cask::DSL::Version, cask: Cask::Cask).returns(Cask::DSL::Version) } + def shortened_version(version, cask:) + if version.before_comma == cask.version.before_comma + version + else + version.before_comma + end + end + sig { params( new_version: BumpVersionParser, @@ -434,52 +480,6 @@ module Homebrew nil end - sig { - params( - contents: String, - name: Symbol, - old_value: T.any(Numeric, String, Symbol), - new_value: T.any(Numeric, String, Symbol), - within: T.nilable(Symbol), - ).returns(String) - } - def replace_cask_stanza_value(contents, name, old_value, new_value, within: nil) - return contents if old_value == new_value - - cask_ast = Utils::AST::CaskAST.new(contents) - replacement_count = cask_ast.replace_stanza_value(name, old_value, new_value, within:) - if replacement_count.zero? - # Treat an already-applied replacement as a successful no-op so the - # per-(os, arch) loop in `replace_version_and_checksum` can yield the - # same general version more than once without raising. - return contents if cask_ast.replace_stanza_value(name, new_value, new_value, within:).positive? - - raise "Could not find '#{name}' stanza with value #{old_value.inspect}!" - end - - cask_ast.process - end - - sig { params(cask: Cask::Cask, new_version: BumpVersionParser).void } - def check_throttle(cask, new_version:) - return unless cask.tap - - throttle_rate = cask.livecheck.throttle - throttle_days = cask.livecheck.throttle_days - return if throttle_rate.nil? && throttle_days.nil? - - version = new_version.arm || new_version.intel || new_version.general - return unless version.is_a?(Cask::DSL::Version) - - return if Livecheck.throttle_allows_bump?(cask, version.to_s, throttle_rate:, throttle_days:) - - throttle_items = [] - throttle_items << "#{throttle_rate} releases on multiples of #{throttle_rate}" if throttle_rate - throttle_items << "#{throttle_days} #{Utils.pluralize("day", throttle_days)}" if throttle_days - - odie "#{cask.token} should only be updated every #{throttle_items.join(" or ")}" - end - sig { params(cask: Cask::Cask, new_version: BumpVersionParser).void } def check_pull_requests(cask, new_version:) tap = cask.tap diff --git a/Library/Homebrew/dev-cmd/bump-formula-pr.rb b/Library/Homebrew/dev-cmd/bump-formula-pr.rb index 157381df9a..27628e5633 100644 --- a/Library/Homebrew/dev-cmd/bump-formula-pr.rb +++ b/Library/Homebrew/dev-cmd/bump-formula-pr.rb @@ -504,6 +504,88 @@ module Homebrew end end + sig { params(formula: Formula, new_version: String).void } + def check_throttle(formula, new_version) + tap = formula.tap + return if tap.nil? + + throttle_rate = formula.livecheck.throttle + throttle_days = formula.livecheck.throttle_days + return if throttle_rate.nil? && throttle_days.nil? + + return if Livecheck.throttle_allows_bump?( + formula, + new_version, + throttle_rate: throttle_rate, + throttle_days: throttle_days, + ) + + throttle_items = [] + throttle_items << "#{throttle_rate} releases on multiples of #{throttle_rate}" if throttle_rate + throttle_items << "#{throttle_days} #{Utils.pluralize("day", throttle_days)}" if throttle_days + + odie "#{formula} should only be updated every #{throttle_items.join(" or ")}" + end + + sig { + params( + formula: Formula, + version: String, + resource_versions: T.nilable(T::Hash[String, T::Hash[Symbol, T.nilable(String)]]), + ).returns(T::Hash[String, Symbol]) + } + def update_matching_version_resources!(formula, version:, resource_versions: nil) + resource_versions ||= {} + formula.resources + .select { |r| r.livecheck.formula == :parent && resource_versions[r.name].blank? } + .to_h { |resource| [resource.name, update_resource_block!(formula, resource, version)] } + end + + sig { + params( + formula: Formula, + resource_versions: T::Hash[String, T::Hash[Symbol, T.nilable(String)]], + ).returns(T::Hash[String, Symbol]) + } + def update_resources!(formula, resource_versions:) + results = {} + + formula.resources.each do |resource| + version_data = resource_versions[resource.name] + next if version_data.blank? + + current_version = version_data[:current_version] + latest_version = version_data[:latest_version] + + if current_version.blank? || latest_version.blank? + opoo "Could not determine versions for resource \"#{resource.name}\"" + results[resource.name] = :version_unknown + next + end + + if current_version == latest_version + results[resource.name] = :up_to_date + next + end + + is_downgraded = Version.new(current_version) > Version.new(latest_version) + + begin + result = update_resource_block!(formula, resource, latest_version) + results[resource.name] = if result == :success && is_downgraded + :downgraded + else + result + end + rescue => e + opoo "Failed to update resource \"#{resource.name}\": #{e}" + results[resource.name] = :fetch_failed + end + end + + results + end + private sig { params(url: String).returns(T.nilable(String)) } @@ -619,29 +701,6 @@ module Homebrew check_pull_requests(formula, tap_remote_repo, version:) unless args.write_only? end - sig { params(formula: Formula, new_version: String).void } - def check_throttle(formula, new_version) - tap = formula.tap - return if tap.nil? - - throttle_rate = formula.livecheck.throttle - throttle_days = formula.livecheck.throttle_days - return if throttle_rate.nil? && throttle_days.nil? - - return if Livecheck.throttle_allows_bump?( - formula, - new_version, - throttle_rate: throttle_rate, - throttle_days: throttle_days, - ) - - throttle_items = [] - throttle_items << "#{throttle_rate} releases on multiples of #{throttle_rate}" if throttle_rate - throttle_items << "#{throttle_days} #{Utils.pluralize("day", throttle_days)}" if throttle_days - - odie "#{formula} should only be updated every #{throttle_items.join(" or ")}" - end - sig { params(formula: Formula, new_formula_version: Version).returns(T.nilable(T::Array[String])) } def alias_update_pair(formula, new_formula_version) versioned_alias = formula.aliases.grep(/^.*@\d+(\.\d+)?$/).first @@ -733,65 +792,6 @@ module Homebrew :success end - sig { - params( - formula: Formula, - version: String, - resource_versions: T.nilable(T::Hash[String, T::Hash[Symbol, T.nilable(String)]]), - ).returns(T::Hash[String, Symbol]) - } - def update_matching_version_resources!(formula, version:, resource_versions: nil) - resource_versions ||= {} - formula.resources - .select { |r| r.livecheck.formula == :parent && resource_versions[r.name].blank? } - .to_h { |resource| [resource.name, update_resource_block!(formula, resource, version)] } - end - - sig { - params( - formula: Formula, - resource_versions: T::Hash[String, T::Hash[Symbol, T.nilable(String)]], - ).returns(T::Hash[String, Symbol]) - } - def update_resources!(formula, resource_versions:) - results = {} - - formula.resources.each do |resource| - version_data = resource_versions[resource.name] - next if version_data.blank? - - current_version = version_data[:current_version] - latest_version = version_data[:latest_version] - - if current_version.blank? || latest_version.blank? - opoo "Could not determine versions for resource \"#{resource.name}\"" - results[resource.name] = :version_unknown - next - end - - if current_version == latest_version - results[resource.name] = :up_to_date - next - end - - is_downgraded = Version.new(current_version) > Version.new(latest_version) - - begin - result = update_resource_block!(formula, resource, latest_version) - results[resource.name] = if result == :success && is_downgraded - :downgraded - else - result - end - rescue => e - opoo "Failed to update resource \"#{resource.name}\": #{e}" - results[resource.name] = :fetch_failed - end - end - - results - end - sig { params(formula: Formula, alias_rename: T.nilable(T::Array[String]), skip_synced_versions: T::Boolean).returns(T::Boolean) diff --git a/Library/Homebrew/dev-cmd/bump.rb b/Library/Homebrew/dev-cmd/bump.rb index 17c7cc464c..10772e6939 100644 --- a/Library/Homebrew/dev-cmd/bump.rb +++ b/Library/Homebrew/dev-cmd/bump.rb @@ -184,62 +184,6 @@ module Homebrew end end - private - - sig { params(formula_or_cask: T.any(Formula, Cask::Cask)).returns(T::Boolean) } - def skip_repology?(formula_or_cask) - return true unless args.repology? - - (ENV["CI"].present? && args.open_pr? && formula_or_cask.livecheck_defined?) || - (formula_or_cask.is_a?(Formula) && formula_or_cask.versioned_formula?) - end - - sig { params(formulae_and_casks: T::Array[T.any(Formula, Cask::Cask)]).void } - def handle_formulae_and_casks(formulae_and_casks) - Livecheck.load_other_tap_strategies(formulae_and_casks) - - ambiguous_casks = [] - if !args.formula? && !args.cask? - ambiguous_casks = formulae_and_casks - .group_by { |item| Livecheck.package_or_resource_name(item, full_name: true) } - .values - .select { |items| items.length > 1 } - .flatten - .grep(Cask::Cask) - end - - ambiguous_names = [] - unless args.full_name? - ambiguous_names = (formulae_and_casks - ambiguous_casks) - .group_by { |item| Livecheck.package_or_resource_name(item) } - .values - .select { |items| items.length > 1 } - .flatten - end - - formulae_and_casks.each_with_index do |formula_or_cask, i| - puts if i.positive? - next if skip_ineligible_formulae!(formula_or_cask) - - use_full_name = args.full_name? || ambiguous_names.include?(formula_or_cask) - name = Livecheck.package_or_resource_name(formula_or_cask, full_name: use_full_name) - repository = if formula_or_cask.is_a?(Formula) - Repology::HOMEBREW_CORE - else - Repology::HOMEBREW_CASK - end - - package_data = Repology.single_package_query(name, repository:) unless skip_repology?(formula_or_cask) - - retrieve_and_display_info_and_open_pr( - formula_or_cask, - name, - package_data&.values&.first || [], - ambiguous_cask: ambiguous_casks.include?(formula_or_cask), - ) - end - end - sig { params(formula_or_cask: T.any(Formula, Cask::Cask)).returns(T::Boolean) } @@ -268,86 +212,6 @@ module Homebrew true end - sig { - params( - formula_or_cask: T.any(Formula, Cask::Cask), - current: T.nilable(T.any(Version, Cask::DSL::Version)), - ).returns(T.any(Version, String)) - } - def livecheck_result(formula_or_cask, current) - name = Livecheck.package_or_resource_name(formula_or_cask) - - referenced_formula_or_cask, = Livecheck.resolve_livecheck_reference( - formula_or_cask, - full_name: false, - debug: false, - ) - - # Check skip conditions for a referenced formula/cask - if referenced_formula_or_cask - skip_info = Livecheck::SkipConditions.referenced_skip_information( - referenced_formula_or_cask, - name, - full_name: false, - verbose: false, - ) - end - - skip_info ||= Livecheck::SkipConditions.skip_information( - formula_or_cask, - full_name: false, - verbose: false, - ) - - if skip_info.present? - skip_status = skip_info[:status] - skip_messages = skip_info[:messages] - skip_message = skip_messages.join("; ") if skip_messages.present? - return "error: #{skip_message}" if skip_status == "error" && skip_message - - return "skipped - #{skip_message || skip_status}" - end - - version_info = Livecheck.latest_version( - formula_or_cask, - referenced_formula_or_cask:, - json: true, full_name: false, verbose: true, debug: false - ) - return "unable to get versions" if version_info.blank? - - if !version_info.key?(:latest_throttled) - version_with_cooldown(version_info, current) || Version.new(version_info[:latest]) - elsif version_info[:latest_throttled].nil? - "unable to get throttled versions" - else - Version.new(version_info[:latest_throttled]) - end - rescue => e - "error: #{e}" - end - - sig { - params( - formula_or_cask: T.any(Formula, Cask::Cask), - name: String, - version: T.nilable(String), - ).returns T.nilable(T.any(T::Array[String], String)) - } - def retrieve_pull_requests(formula_or_cask, name, version: nil) - tap_remote_repo = formula_or_cask.tap&.remote_repository || formula_or_cask.tap&.full_name - odie "unexpected nil tap remote repository" if tap_remote_repo.nil? - - pull_requests = begin - GitHub.fetch_pull_requests(name, tap_remote_repo, version:) - rescue GitHub::API::ValidationFailedError => e - odebug "Error fetching pull requests for #{formula_or_cask} #{name}: #{e}" - nil - end - return if pull_requests.blank? - - pull_requests.map { |pr| "#{pr["title"]} (#{Formatter.url(pr["html_url"])})" }.join(", ") - end - sig { params( formula_or_cask: T.any(Formula, Cask::Cask), @@ -527,68 +391,6 @@ module Homebrew ) end - sig { - params( - formula: Formula, - formula_latest_version: String, - ).returns(T::Array[ResourceVersionInfo]) - } - def collect_resource_versions(formula, formula_latest_version) - resource_versions = [] - - formula.resources.each do |resource| - next unless resource.livecheck_defined? - next if resource.livecheck.skip? - - # Resources that reference :parent track the formula version directly - if resource.livecheck.formula == :parent - current = resource.version.to_s - resource_versions << ResourceVersionInfo.new( - name: resource.name, - current_version: current, - latest_version: formula_latest_version, - outdated: Version.new(current) < Version.new(formula_latest_version), - newer_than_upstream: Version.new(current) > Version.new(formula_latest_version), - ) - next - end - - resource_info = Livecheck.resource_version( - resource, - formula_latest_version, - json: true, - full_name: false, - debug: false, - quiet: true, - verbose: false, - ) - - if resource_info.empty? || resource_info[:status] == "error" - resource_versions << ResourceVersionInfo.new( - name: resource.name, - current_version: resource.version.to_s, - latest_version: nil, - outdated: false, - newer_than_upstream: false, - ) - next - end - - version_info = resource_info[:version] - next if version_info.blank? - - resource_versions << ResourceVersionInfo.new( - name: resource.name, - current_version: version_info[:current], - latest_version: version_info[:latest], - outdated: version_info[:outdated] == true, - newer_than_upstream: version_info[:newer_than_upstream] == true, - ) - end - - resource_versions - end - sig { params( formula_or_cask: T.any(Formula, Cask::Cask), @@ -876,42 +678,6 @@ module Homebrew value.match?(LIVECHECK_MESSAGE_REGEX) end - sig { - params( - formula: Formula, - new_version: T.nilable(T.any(Version, Cask::DSL::Version)), - ).returns(T::Array[String]) - } - def synced_with(formula, new_version) - synced_with = [] - - formula.tap&.synced_versions_formulae&.each do |synced_formulae| - next unless synced_formulae.include?(formula.name) - - synced_formulae.each do |synced_formula| - synced_formula = Formulary.factory(synced_formula) - next if synced_formula == formula.name - - synced_with << synced_formula.name if synced_formula.version != new_version - end - end - - synced_with - end - - sig { params(tap: Tap, casks: T::Boolean).returns(T::Array[T.any(Formula, Cask::Cask)]) } - def autobumped_formulae_or_casks(tap, casks: false) - autobump_list = tap.autobump - autobump_list.map do |name| - qualified_name = "#{tap.name}/#{name}" - if casks - Cask::CaskLoader.load(qualified_name) - else - Formulary.factory(qualified_name) - end - end - end - # Identifies the highest upstream version that has been released before # the cooldown interval. # @@ -1028,6 +794,240 @@ module Homebrew end end end + + private + + sig { params(formula_or_cask: T.any(Formula, Cask::Cask)).returns(T::Boolean) } + def skip_repology?(formula_or_cask) + return true unless args.repology? + + (ENV["CI"].present? && args.open_pr? && formula_or_cask.livecheck_defined?) || + (formula_or_cask.is_a?(Formula) && formula_or_cask.versioned_formula?) + end + + sig { params(formulae_and_casks: T::Array[T.any(Formula, Cask::Cask)]).void } + def handle_formulae_and_casks(formulae_and_casks) + Livecheck.load_other_tap_strategies(formulae_and_casks) + + ambiguous_casks = [] + if !args.formula? && !args.cask? + ambiguous_casks = formulae_and_casks + .group_by { |item| Livecheck.package_or_resource_name(item, full_name: true) } + .values + .select { |items| items.length > 1 } + .flatten + .grep(Cask::Cask) + end + + ambiguous_names = [] + unless args.full_name? + ambiguous_names = (formulae_and_casks - ambiguous_casks) + .group_by { |item| Livecheck.package_or_resource_name(item) } + .values + .select { |items| items.length > 1 } + .flatten + end + + formulae_and_casks.each_with_index do |formula_or_cask, i| + puts if i.positive? + next if skip_ineligible_formulae!(formula_or_cask) + + use_full_name = args.full_name? || ambiguous_names.include?(formula_or_cask) + name = Livecheck.package_or_resource_name(formula_or_cask, full_name: use_full_name) + repository = if formula_or_cask.is_a?(Formula) + Repology::HOMEBREW_CORE + else + Repology::HOMEBREW_CASK + end + + package_data = Repology.single_package_query(name, repository:) unless skip_repology?(formula_or_cask) + + retrieve_and_display_info_and_open_pr( + formula_or_cask, + name, + package_data&.values&.first || [], + ambiguous_cask: ambiguous_casks.include?(formula_or_cask), + ) + end + end + + sig { + params( + formula_or_cask: T.any(Formula, Cask::Cask), + current: T.nilable(T.any(Version, Cask::DSL::Version)), + ).returns(T.any(Version, String)) + } + def livecheck_result(formula_or_cask, current) + name = Livecheck.package_or_resource_name(formula_or_cask) + + referenced_formula_or_cask, = Livecheck.resolve_livecheck_reference( + formula_or_cask, + full_name: false, + debug: false, + ) + + # Check skip conditions for a referenced formula/cask + if referenced_formula_or_cask + skip_info = Livecheck::SkipConditions.referenced_skip_information( + referenced_formula_or_cask, + name, + full_name: false, + verbose: false, + ) + end + + skip_info ||= Livecheck::SkipConditions.skip_information( + formula_or_cask, + full_name: false, + verbose: false, + ) + + if skip_info.present? + skip_status = skip_info[:status] + skip_messages = skip_info[:messages] + skip_message = skip_messages.join("; ") if skip_messages.present? + return "error: #{skip_message}" if skip_status == "error" && skip_message + + return "skipped - #{skip_message || skip_status}" + end + + version_info = Livecheck.latest_version( + formula_or_cask, + referenced_formula_or_cask:, + json: true, full_name: false, verbose: true, debug: false + ) + return "unable to get versions" if version_info.blank? + + if !version_info.key?(:latest_throttled) + version_with_cooldown(version_info, current) || Version.new(version_info[:latest]) + elsif version_info[:latest_throttled].nil? + "unable to get throttled versions" + else + Version.new(version_info[:latest_throttled]) + end + rescue => e + "error: #{e}" + end + + sig { + params( + formula_or_cask: T.any(Formula, Cask::Cask), + name: String, + version: T.nilable(String), + ).returns T.nilable(T.any(T::Array[String], String)) + } + def retrieve_pull_requests(formula_or_cask, name, version: nil) + tap_remote_repo = formula_or_cask.tap&.remote_repository || formula_or_cask.tap&.full_name + odie "unexpected nil tap remote repository" if tap_remote_repo.nil? + + pull_requests = begin + GitHub.fetch_pull_requests(name, tap_remote_repo, version:) + rescue GitHub::API::ValidationFailedError => e + odebug "Error fetching pull requests for #{formula_or_cask} #{name}: #{e}" + nil + end + return if pull_requests.blank? + + pull_requests.map { |pr| "#{pr["title"]} (#{Formatter.url(pr["html_url"])})" }.join(", ") + end + + sig { + params( + formula: Formula, + formula_latest_version: String, + ).returns(T::Array[ResourceVersionInfo]) + } + def collect_resource_versions(formula, formula_latest_version) + resource_versions = [] + + formula.resources.each do |resource| + next unless resource.livecheck_defined? + next if resource.livecheck.skip? + + # Resources that reference :parent track the formula version directly + if resource.livecheck.formula == :parent + current = resource.version.to_s + resource_versions << ResourceVersionInfo.new( + name: resource.name, + current_version: current, + latest_version: formula_latest_version, + outdated: Version.new(current) < Version.new(formula_latest_version), + newer_than_upstream: Version.new(current) > Version.new(formula_latest_version), + ) + next + end + + resource_info = Livecheck.resource_version( + resource, + formula_latest_version, + json: true, + full_name: false, + debug: false, + quiet: true, + verbose: false, + ) + + if resource_info.empty? || resource_info[:status] == "error" + resource_versions << ResourceVersionInfo.new( + name: resource.name, + current_version: resource.version.to_s, + latest_version: nil, + outdated: false, + newer_than_upstream: false, + ) + next + end + + version_info = resource_info[:version] + next if version_info.blank? + + resource_versions << ResourceVersionInfo.new( + name: resource.name, + current_version: version_info[:current], + latest_version: version_info[:latest], + outdated: version_info[:outdated] == true, + newer_than_upstream: version_info[:newer_than_upstream] == true, + ) + end + + resource_versions + end + + sig { + params( + formula: Formula, + new_version: T.nilable(T.any(Version, Cask::DSL::Version)), + ).returns(T::Array[String]) + } + def synced_with(formula, new_version) + synced_with = [] + + formula.tap&.synced_versions_formulae&.each do |synced_formulae| + next unless synced_formulae.include?(formula.name) + + synced_formulae.each do |synced_formula| + synced_formula = Formulary.factory(synced_formula) + next if synced_formula == formula.name + + synced_with << synced_formula.name if synced_formula.version != new_version + end + end + + synced_with + end + + sig { params(tap: Tap, casks: T::Boolean).returns(T::Array[T.any(Formula, Cask::Cask)]) } + def autobumped_formulae_or_casks(tap, casks: false) + autobump_list = tap.autobump + autobump_list.map do |name| + qualified_name = "#{tap.name}/#{name}" + if casks + Cask::CaskLoader.load(qualified_name) + else + Formulary.factory(qualified_name) + end + end + end end end end diff --git a/Library/Homebrew/dev-cmd/contributions.rb b/Library/Homebrew/dev-cmd/contributions.rb index b2f0e51bb5..86b7a7972a 100644 --- a/Library/Homebrew/dev-cmd/contributions.rb +++ b/Library/Homebrew/dev-cmd/contributions.rb @@ -227,8 +227,6 @@ module Homebrew puts generate_csv(grand_totals) end - private - sig { params(repository_refs: T::Hash[String, [Pathname, String]], to: String) .returns([T::Hash[String, String], T::Hash[String, T::Boolean], T::Hash[String, T.nilable(String)]]) @@ -268,118 +266,6 @@ module Homebrew [user_names, lead_maintainers, maintainer_since_dates] end - sig { - params( - results: T::Hash[String, T::Hash[String, T::Hash[Symbol, Integer]]], - grand_totals: T::Hash[String, T::Hash[Symbol, Integer]], - user_names: T::Hash[String, String], - lead_maintainers: T::Hash[String, T::Boolean], - maintainer_since_dates: T::Hash[String, T.nilable(String)], - to: String, - ).returns(String) - } - def generate_maintainer_report_csv(results, grand_totals, user_names, lead_maintainers, maintainer_since_dates, - to) - require "csv" - - rows = results.sort_by do |user, _| - qualifying_total = contribution_count(grand_totals.fetch(user).slice(*QUALIFYING_CONTRIBUTION_TYPES)) - [-qualifying_total, user.downcase] - end - rows.map! do |user, user_repositories| - grand_total = grand_totals.fetch(user) - repository_qualifying_totals = user_repositories.transform_values do |counts| - contribution_count(counts.slice(*QUALIFYING_CONTRIBUTION_TYPES)) - end - qualifying_total = contribution_count(grand_total.slice(*QUALIFYING_CONTRIBUTION_TYPES)) - maintainer_activity_met = qualifying_total >= MAINTAINER_ACTIVITY_THRESHOLD - maintainer_since = maintainer_since_dates.fetch(user) - maintainer_since_date = Date.iso8601(maintainer_since) if maintainer_since - period_end = Date.iso8601(to) - lead_maintainer = lead_maintainers.key?(user.downcase) - lead_activity_met = lead_activity_met?(user_repositories) - new_role = if lead_activity_met && - (lead_maintainer || - (maintainer_since_date && maintainer_since_date <= period_end.prev_year(3))) - "Lead Maintainer" - elsif maintainer_activity_met - "Maintainer" - else - "None" - end - - capped = grand_total.fetch(:merged_pr_author_hit_cap, 0).positive? || - grand_total.fetch(:approved_pr_review_hit_cap, 0).positive? - capped ||= user_repositories.any? do |_, counts| - counts.fetch(:approved_pr_review) >= MAX_PR_SEARCH || - counts.except(:approved_pr_review).values.any? do |count| - count >= MAX_CONTRIBUTIONS - end - end - - [ - user, - user_names.fetch(user), - maintainer_since, - maintainer_since_date ? [(period_end - maintainer_since_date).to_i, 0].max : nil, - *PRIMARY_REPOS.flat_map do |repository| - counts = user_repositories.fetch(repository) - [*counts.values_at(*CONTRIBUTION_TYPES.keys), repository_qualifying_totals.fetch(repository)] - end, - qualifying_total, - maintainer_activity_met, - lead_activity_met, - capped, - lead_maintainer ? "Lead Maintainer" : "Maintainer", - new_role, - ] - end - CSV.generate do |csv| - csv << [ - "username", "name", "since", "tenure days", - *PRIMARY_REPOS.flat_map do |repository| - repository = repository.delete_prefix("Homebrew/").delete_prefix("homebrew-") - [ - "#{repository} authored", "#{repository} merged", "#{repository} PRs", - "#{repository} reviews", "#{repository} coauthored", "#{repository} total" - ] - end, - "total", "maintainer met", "lead met", "capped", "role", "new role" - ] - rows.each { |row| csv << row } - end - end - - sig { params(repositories: T::Array[String], required: T::Boolean).returns(T::Hash[String, [Pathname, String]]) } - def prepare_contribution_repositories(repositories, required:) - require "utils/git" - - repository_refs = T.let({}, T::Hash[String, [Pathname, String]]) - repositories.each do |repository| - repository_path, tap = repository_path_and_tap(repository) - if repository_path && tap && !repository_path.exist? - opoo "Repository #{repository} not yet tapped! Tapping it now..." - tap.install(force: true) - end - unless repository_path&.exist? - odie "Could not find a local Git repository for #{repository}." if required - next - end - - $stderr.puts "Fetching latest commits for #{repository}..." - system_command!(Utils::Git.git, - args: ["-C", repository_path, "fetch", "--quiet", "--force", "origin", - "+refs/heads/*:refs/remotes/origin/*"], - print_stderr: false) - system_command!(Utils::Git.git, - args: ["-C", repository_path, "remote", "set-head", "origin", "--auto"], - print_stderr: false) - - repository_refs[repository] = [repository_path, "origin/HEAD"] - end - repository_refs - end - sig { params(repository_path: Pathname, ref: String, user: String, name: String).returns(T.nilable(String)) } def maintainer_since(repository_path, ref, user, name) require "utils/git" @@ -406,12 +292,6 @@ module Homebrew nil end - sig { params(readme: String, user: String, name: String).returns(T::Boolean) } - def readme_mentions?(readme, user, name) - readme = readme.dup.force_encoding(Encoding::UTF_8) - readme.include?("https://github.com/#{user}") || readme.include?(name) - end - sig { params( organisation: String, @@ -559,38 +439,6 @@ module Homebrew results end - sig { - params( - pull_request: T::Hash[String, T.untyped], - authored_pull_requests: T::Set[String], - merged_pull_requests: T::Set[String], - ).void - } - def add_merged_pull_request_id(pull_request, authored_pull_requests, merged_pull_requests) - number = pull_request["number"] - return unless number.is_a?(Integer) - - pull_request_id = number.to_s - authored_pull_requests << pull_request_id - merged_pull_requests << pull_request_id - end - - sig { - params( - counts: T::Hash[Symbol, Integer], - authored_pull_requests: T::Set[String], - merged_pull_requests: T::Set[String], - ).void - } - def update_merged_pull_request_counts(counts, authored_pull_requests, merged_pull_requests) - unless authored_pull_requests.empty? - counts[:merged_pr_author] = [authored_pull_requests.length, MAX_CONTRIBUTIONS].min - end - return if merged_pull_requests.empty? - - counts[:merged_pr] = [merged_pull_requests.length, MAX_CONTRIBUTIONS].min - end - sig { params(user: String, to: String).returns(T.nilable(String)) } def github_username_for(user, to:) return user unless user.include?("@") @@ -731,6 +579,158 @@ module Homebrew counts end + private + + sig { + params( + results: T::Hash[String, T::Hash[String, T::Hash[Symbol, Integer]]], + grand_totals: T::Hash[String, T::Hash[Symbol, Integer]], + user_names: T::Hash[String, String], + lead_maintainers: T::Hash[String, T::Boolean], + maintainer_since_dates: T::Hash[String, T.nilable(String)], + to: String, + ).returns(String) + } + def generate_maintainer_report_csv(results, grand_totals, user_names, lead_maintainers, maintainer_since_dates, + to) + require "csv" + + rows = results.sort_by do |user, _| + qualifying_total = contribution_count(grand_totals.fetch(user).slice(*QUALIFYING_CONTRIBUTION_TYPES)) + [-qualifying_total, user.downcase] + end + rows.map! do |user, user_repositories| + grand_total = grand_totals.fetch(user) + repository_qualifying_totals = user_repositories.transform_values do |counts| + contribution_count(counts.slice(*QUALIFYING_CONTRIBUTION_TYPES)) + end + qualifying_total = contribution_count(grand_total.slice(*QUALIFYING_CONTRIBUTION_TYPES)) + maintainer_activity_met = qualifying_total >= MAINTAINER_ACTIVITY_THRESHOLD + maintainer_since = maintainer_since_dates.fetch(user) + maintainer_since_date = Date.iso8601(maintainer_since) if maintainer_since + period_end = Date.iso8601(to) + lead_maintainer = lead_maintainers.key?(user.downcase) + lead_activity_met = lead_activity_met?(user_repositories) + new_role = if lead_activity_met && + (lead_maintainer || + (maintainer_since_date && maintainer_since_date <= period_end.prev_year(3))) + "Lead Maintainer" + elsif maintainer_activity_met + "Maintainer" + else + "None" + end + + capped = grand_total.fetch(:merged_pr_author_hit_cap, 0).positive? || + grand_total.fetch(:approved_pr_review_hit_cap, 0).positive? + capped ||= user_repositories.any? do |_, counts| + counts.fetch(:approved_pr_review) >= MAX_PR_SEARCH || + counts.except(:approved_pr_review).values.any? do |count| + count >= MAX_CONTRIBUTIONS + end + end + + [ + user, + user_names.fetch(user), + maintainer_since, + maintainer_since_date ? [(period_end - maintainer_since_date).to_i, 0].max : nil, + *PRIMARY_REPOS.flat_map do |repository| + counts = user_repositories.fetch(repository) + [*counts.values_at(*CONTRIBUTION_TYPES.keys), repository_qualifying_totals.fetch(repository)] + end, + qualifying_total, + maintainer_activity_met, + lead_activity_met, + capped, + lead_maintainer ? "Lead Maintainer" : "Maintainer", + new_role, + ] + end + CSV.generate do |csv| + csv << [ + "username", "name", "since", "tenure days", + *PRIMARY_REPOS.flat_map do |repository| + repository = repository.delete_prefix("Homebrew/").delete_prefix("homebrew-") + [ + "#{repository} authored", "#{repository} merged", "#{repository} PRs", + "#{repository} reviews", "#{repository} coauthored", "#{repository} total" + ] + end, + "total", "maintainer met", "lead met", "capped", "role", "new role" + ] + rows.each { |row| csv << row } + end + end + + sig { params(repositories: T::Array[String], required: T::Boolean).returns(T::Hash[String, [Pathname, String]]) } + def prepare_contribution_repositories(repositories, required:) + require "utils/git" + + repository_refs = T.let({}, T::Hash[String, [Pathname, String]]) + repositories.each do |repository| + repository_path, tap = repository_path_and_tap(repository) + if repository_path && tap && !repository_path.exist? + opoo "Repository #{repository} not yet tapped! Tapping it now..." + tap.install(force: true) + end + unless repository_path&.exist? + odie "Could not find a local Git repository for #{repository}." if required + next + end + + $stderr.puts "Fetching latest commits for #{repository}..." + system_command!(Utils::Git.git, + args: ["-C", repository_path, "fetch", "--quiet", "--force", "origin", + "+refs/heads/*:refs/remotes/origin/*"], + print_stderr: false) + system_command!(Utils::Git.git, + args: ["-C", repository_path, "remote", "set-head", "origin", "--auto"], + print_stderr: false) + + repository_refs[repository] = [repository_path, "origin/HEAD"] + end + repository_refs + end + + sig { params(readme: String, user: String, name: String).returns(T::Boolean) } + def readme_mentions?(readme, user, name) + readme = readme.dup.force_encoding(Encoding::UTF_8) + readme.include?("https://github.com/#{user}") || readme.include?(name) + end + + sig { + params( + pull_request: T::Hash[String, T.untyped], + authored_pull_requests: T::Set[String], + merged_pull_requests: T::Set[String], + ).void + } + def add_merged_pull_request_id(pull_request, authored_pull_requests, merged_pull_requests) + number = pull_request["number"] + return unless number.is_a?(Integer) + + pull_request_id = number.to_s + authored_pull_requests << pull_request_id + merged_pull_requests << pull_request_id + end + + sig { + params( + counts: T::Hash[Symbol, Integer], + authored_pull_requests: T::Set[String], + merged_pull_requests: T::Set[String], + ).void + } + def update_merged_pull_request_counts(counts, authored_pull_requests, merged_pull_requests) + unless authored_pull_requests.empty? + counts[:merged_pr_author] = [authored_pull_requests.length, MAX_CONTRIBUTIONS].min + end + return if merged_pull_requests.empty? + + counts[:merged_pr] = [merged_pull_requests.length, MAX_CONTRIBUTIONS].min + end + sig { params(name: String, email: String, identity_users: T::Hash[String, String]).returns(T.nilable(String)) } diff --git a/Library/Homebrew/dev-cmd/generate-cask-ci-matrix.rb b/Library/Homebrew/dev-cmd/generate-cask-ci-matrix.rb index d4870424c9..89bc67dc0a 100644 --- a/Library/Homebrew/dev-cmd/generate-cask-ci-matrix.rb +++ b/Library/Homebrew/dev-cmd/generate-cask-ci-matrix.rb @@ -192,6 +192,28 @@ module Homebrew end end + sig { params(cask: Cask::Cask).returns([T::Array[T::Hash[Symbol, T.any(Symbol, String)]], T::Boolean]) } + def runners(cask:) + filtered_runners = filter_runners(cask) + + filtered_macos_found = filtered_runners.keys.any? do |runner| + cask.to_hash_with_variations["variations"].key?(runner.fetch(:symbol).to_sym) + end + + if filtered_macos_found + # If the cask varies on a MacOS version, test it on every possible macOS version. + [filtered_runners.keys, true] + else + macos_runners, linux_runners = filtered_runners.partition do |runner, _| + runner.fetch(:symbol) != :linux + end + selected_runners = macos_runners.group_by { |runner, _| runner.fetch(:arch) }.map do |_, runners| + random_runner(runners.to_h) + end + linux_runners.map(&:first) + [selected_runners, false] + end + end + private sig { params(cask: Cask::Cask, os: Symbol).returns(T::Array[Symbol]) } @@ -223,28 +245,6 @@ module Homebrew max_runner.first end - sig { params(cask: Cask::Cask).returns([T::Array[T::Hash[Symbol, T.any(Symbol, String)]], T::Boolean]) } - def runners(cask:) - filtered_runners = filter_runners(cask) - - filtered_macos_found = filtered_runners.keys.any? do |runner| - cask.to_hash_with_variations["variations"].key?(runner.fetch(:symbol).to_sym) - end - - if filtered_macos_found - # If the cask varies on a MacOS version, test it on every possible macOS version. - [filtered_runners.keys, true] - else - macos_runners, linux_runners = filtered_runners.partition do |runner, _| - runner.fetch(:symbol) != :linux - end - selected_runners = macos_runners.group_by { |runner, _| runner.fetch(:arch) }.map do |_, runners| - random_runner(runners.to_h) - end + linux_runners.map(&:first) - [selected_runners, false] - end - end - sig { params(tap: T.nilable(Tap), labels: T::Array[String], cask_names: T::Array[String], skip_install: T::Boolean, new_cask: T::Boolean).returns(T::Array[T::Hash[Symbol, diff --git a/Library/Homebrew/dev-cmd/generate-zap.rb b/Library/Homebrew/dev-cmd/generate-zap.rb index d20161f658..6b475adda3 100644 --- a/Library/Homebrew/dev-cmd/generate-zap.rb +++ b/Library/Homebrew/dev-cmd/generate-zap.rb @@ -129,8 +129,6 @@ module Homebrew puts format_stanza(trash: trash_paths, delete: delete_paths, rmdir: rmdir_paths) end - private - sig { params(cask: Cask::Cask).returns(T::Array[String]) } def resolve_patterns_from_cask(cask) app_artifact = cask.artifacts.find { |a| a.is_a?(Cask::Artifact::App) } @@ -144,11 +142,6 @@ module Homebrew end end - sig { params(patterns: T::Array[String]).returns(String) } - def format_patterns(patterns) - patterns.map { |pattern| "\"#{pattern}\"" }.to_sentence - end - sig { params(app_artifact: Cask::Artifact::App).returns(T::Array[String]) } def bundle_identifiers(app_artifact) info_plist = app_artifact.target/"Contents/Info.plist" @@ -238,35 +231,6 @@ module Homebrew result.uniq.sort end - sig { params(basenames: T::Array[String]).returns(T::Array[String]) } - def find_wildcard_groups(basenames) - return basenames if basenames.size <= 1 - - used = Array.new(basenames.size, false) - result = [] - - basenames.each_with_index do |name, i| - next if used[i] - - group_indices = [i] - basenames.each_with_index do |other, j| - next if i == j || used[j] - next unless other.start_with?(name) - - group_indices << j - end - - if group_indices.size > 1 - result << "#{name}*" - group_indices.each { |idx| used[idx] = true } - else - result << name - end - end - - result - end - sig { params(paths: T::Array[String]).returns(T::Array[String]) } def replace_uuids(paths) paths.map { |p| p.gsub(UUID_PATTERN, "*") }.uniq.sort @@ -321,6 +285,42 @@ module Homebrew .prepend("zap ") end + private + + sig { params(patterns: T::Array[String]).returns(String) } + def format_patterns(patterns) + patterns.map { |pattern| "\"#{pattern}\"" }.to_sentence + end + + sig { params(basenames: T::Array[String]).returns(T::Array[String]) } + def find_wildcard_groups(basenames) + return basenames if basenames.size <= 1 + + used = Array.new(basenames.size, false) + result = [] + + basenames.each_with_index do |name, i| + next if used[i] + + group_indices = [i] + basenames.each_with_index do |other, j| + next if i == j || used[j] + next unless other.start_with?(name) + + group_indices << j + end + + if group_indices.size > 1 + result << "#{name}*" + group_indices.each { |idx| used[idx] = true } + else + result << name + end + end + + result + end + sig { params(key: String, paths: T::Array[String]).returns(String) } def format_directive(key, paths) if paths.size == 1 diff --git a/Library/Homebrew/dev-cmd/release.rb b/Library/Homebrew/dev-cmd/release.rb index 90d5ea1e9d..9c1a404361 100644 --- a/Library/Homebrew/dev-cmd/release.rb +++ b/Library/Homebrew/dev-cmd/release.rb @@ -215,8 +215,6 @@ module Homebrew exec_browser release_url end - private - sig { params(name: String).returns(T::Array[T::Hash[String, T.untyped]]) } def matching_releases(name) releases_url = "#{GitHub::API_URL}/repos/Homebrew/brew/releases?per_page=#{GitHub::MAX_PER_PAGE}" @@ -240,6 +238,8 @@ module Homebrew end end + private + sig { params(releases: T::Array[T::Hash[String, T.untyped]]).returns(T::Array[String]) } def release_urls(releases) releases.filter_map do |release| diff --git a/Library/Homebrew/dev-cmd/tests.rb b/Library/Homebrew/dev-cmd/tests.rb index 6a43c7b8d2..95bef3356d 100644 --- a/Library/Homebrew/dev-cmd/tests.rb +++ b/Library/Homebrew/dev-cmd/tests.rb @@ -264,47 +264,9 @@ module Homebrew end end - private - - sig { params(bundle_args: T::Array[String]).returns(T::Array[String]) } - def os_bundle_args(bundle_args) - # for generic tests, remove macOS or Linux specific tests - non_linux_bundle_args(non_macos_bundle_args(bundle_args)) - end - - sig { params(bundle_args: T::Array[String]).returns(T::Array[String]) } - def non_macos_bundle_args(bundle_args) - bundle_args << "--tag" << "~needs_homebrew_core" if ENV["CI"] - bundle_args << "--tag" << "~needs_svnadmin" unless args.online? - bundle_args << "--tag" << "~needs_svn" unless args.online? - - bundle_args << "--tag" << "~needs_macos" << "--tag" << "~cask" - end - - sig { params(bundle_args: T::Array[String]).returns(T::Array[String]) } - def non_linux_bundle_args(bundle_args) - bundle_args << "--tag" << "~needs_linux" << "--tag" << "~needs_systemd" - end - sig { void } def check_test_environment!; end - sig { params(files: T::Array[String]).returns(T::Array[String]) } - def os_files(files) - # for generic tests, remove macOS or Linux specific files - non_linux_files(non_macos_files(files)) - end - - sig { params(files: T::Array[String]).returns(T::Array[String]) } - def non_macos_files(files) - files.grep_v(%r{^test/(os/mac|cask)(/.*|_spec\.rb)$}) - end - - sig { params(files: T::Array[String]).returns(T::Array[String]) } - def non_linux_files(files) - files.grep_v(%r{^test/os/linux(/.*|_spec\.rb)$}) - end - sig { returns(T::Array[String]) } def changed_test_files changed_files = Utils::Git.changed_files(HOMEBREW_REPOSITORY) @@ -331,6 +293,44 @@ module Homebrew .map(&:to_s) end + private + + sig { params(bundle_args: T::Array[String]).returns(T::Array[String]) } + def os_bundle_args(bundle_args) + # for generic tests, remove macOS or Linux specific tests + non_linux_bundle_args(non_macos_bundle_args(bundle_args)) + end + + sig { params(bundle_args: T::Array[String]).returns(T::Array[String]) } + def non_macos_bundle_args(bundle_args) + bundle_args << "--tag" << "~needs_homebrew_core" if ENV["CI"] + bundle_args << "--tag" << "~needs_svnadmin" unless args.online? + bundle_args << "--tag" << "~needs_svn" unless args.online? + + bundle_args << "--tag" << "~needs_macos" << "--tag" << "~cask" + end + + sig { params(bundle_args: T::Array[String]).returns(T::Array[String]) } + def non_linux_bundle_args(bundle_args) + bundle_args << "--tag" << "~needs_linux" << "--tag" << "~needs_systemd" + end + + sig { params(files: T::Array[String]).returns(T::Array[String]) } + def os_files(files) + # for generic tests, remove macOS or Linux specific files + non_linux_files(non_macos_files(files)) + end + + sig { params(files: T::Array[String]).returns(T::Array[String]) } + def non_macos_files(files) + files.grep_v(%r{^test/(os/mac|cask)(/.*|_spec\.rb)$}) + end + + sig { params(files: T::Array[String]).returns(T::Array[String]) } + def non_linux_files(files) + files.grep_v(%r{^test/os/linux(/.*|_spec\.rb)$}) + end + sig { params(filestub: String).returns(T::Array[Pathname]) } def shared_context_test_files(filestub) case filestub diff --git a/Library/Homebrew/download_queue.rb b/Library/Homebrew/download_queue.rb index 2ab33864f8..68f96d1025 100644 --- a/Library/Homebrew/download_queue.rb +++ b/Library/Homebrew/download_queue.rb @@ -268,6 +268,11 @@ module Homebrew pool.wait_for_termination end + sig { returns(T::Hash[Downloadable, Concurrent::Promises::Future]) } + def downloads + @downloads ||= T.let({}, T.nilable(T::Hash[Downloadable, Concurrent::Promises::Future])) + end + private sig { params(downloadable: Downloadable, check_attestation: T::Boolean).void } @@ -344,11 +349,6 @@ module Homebrew tty && !@dumb_tty end - sig { returns(T::Hash[Downloadable, Concurrent::Promises::Future]) } - def downloads - @downloads ||= T.let({}, T.nilable(T::Hash[Downloadable, Concurrent::Promises::Future])) - end - sig { params(future: Concurrent::Promises::Future).returns(T.nilable(String)) } def status_from_future(future) case future.state diff --git a/Library/Homebrew/download_strategy/abstract_file_download_strategy.rb b/Library/Homebrew/download_strategy/abstract_file_download_strategy.rb index 158eb1800a..90c0543516 100644 --- a/Library/Homebrew/download_strategy/abstract_file_download_strategy.rb +++ b/Library/Homebrew/download_strategy/abstract_file_download_strategy.rb @@ -63,21 +63,6 @@ class AbstractFileDownloadStrategy < AbstractDownloadStrategy FileUtils.ln_s target_cached_location.relative_path_from(symlink_location.dirname), symlink_location, force: true end - private - - sig { returns(String) } - def resolved_basename - _, resolved_basename = resolved_url_and_basename - resolved_basename - end - - sig { returns([String, String]) } - def resolved_url_and_basename - return T.must(@resolved_url_and_basename) if defined?(@resolved_url_and_basename) - - T.must(@resolved_url_and_basename = T.let([url, parse_basename(url)], T.nilable([String, String]))) - end - sig { params(url: String, search_query: T::Boolean).returns(String) } def parse_basename(url, search_query: true) components = { path: T.let([], T::Array[String]), query: T.let([], T::Array[String]) } @@ -125,4 +110,19 @@ class AbstractFileDownloadStrategy < AbstractDownloadStrategy File.basename(filename) end + + private + + sig { returns(String) } + def resolved_basename + _, resolved_basename = resolved_url_and_basename + resolved_basename + end + + sig { returns([String, String]) } + def resolved_url_and_basename + return T.must(@resolved_url_and_basename) if defined?(@resolved_url_and_basename) + + T.must(@resolved_url_and_basename = T.let([url, parse_basename(url)], T.nilable([String, String]))) + end end diff --git a/Library/Homebrew/download_strategy/curl_download_strategy.rb b/Library/Homebrew/download_strategy/curl_download_strategy.rb index 58d1342216..465ad586d7 100644 --- a/Library/Homebrew/download_strategy/curl_download_strategy.rb +++ b/Library/Homebrew/download_strategy/curl_download_strategy.rb @@ -146,6 +146,32 @@ class CurlDownloadStrategy < AbstractFileDownloadStrategy [time, T.must(file_size)] end + sig { void } + def allow_deferred_environment_expansion! + @expand_deferred_environment = true + end + + # Curl options to be always passed to curl, + # with raw head calls (`curl --head`) or with actual `fetch`. + sig { returns(T::Array[String]) } + def _curl_args + args = [] + + args += ["-b", meta.fetch(:cookies).map { |k, v| "#{k}=#{v}" }.join(";")] if meta.key?(:cookies) + + args += ["-e", meta.fetch(:referer)] if meta.key?(:referer) + + args += ["--user", meta.fetch(:user)] if meta.key?(:user) + + if meta.fetch(:headers, []).any? { |header| header.include?(EnvSensitive::DEFERRED_PLACEHOLDER_PREFIX) } + args += ["--max-redirs", "0"] + end + + args += expand_deferred_environment_args(meta.fetch(:headers, [])).flat_map { |h| ["--header", h.strip] } + + args + end + private sig { params(timeout: T.nilable(T.any(Float, Integer))).returns([String, String]) } @@ -262,11 +288,6 @@ class CurlDownloadStrategy < AbstractFileDownloadStrategy curl_download resolved_url, to:, try_partial: @try_partial, timeout: end - sig { void } - def allow_deferred_environment_expansion! - @expand_deferred_environment = true - end - sig { params(args: T::Array[String]).returns(T::Array[String]) } def expand_deferred_environment_args(args) return args unless @expand_deferred_environment @@ -276,27 +297,6 @@ class CurlDownloadStrategy < AbstractFileDownloadStrategy end end - # Curl options to be always passed to curl, - # with raw head calls (`curl --head`) or with actual `fetch`. - sig { returns(T::Array[String]) } - def _curl_args - args = [] - - args += ["-b", meta.fetch(:cookies).map { |k, v| "#{k}=#{v}" }.join(";")] if meta.key?(:cookies) - - args += ["-e", meta.fetch(:referer)] if meta.key?(:referer) - - args += ["--user", meta.fetch(:user)] if meta.key?(:user) - - if meta.fetch(:headers, []).any? { |header| header.include?(EnvSensitive::DEFERRED_PLACEHOLDER_PREFIX) } - args += ["--max-redirs", "0"] - end - - args += expand_deferred_environment_args(meta.fetch(:headers, [])).flat_map { |h| ["--header", h.strip] } - - args - end - sig { returns(T::Hash[Symbol, T.any(String, Symbol)]) } def _curl_opts meta.slice(:user_agent) diff --git a/Library/Homebrew/download_strategy/git_download_strategy.rb b/Library/Homebrew/download_strategy/git_download_strategy.rb index f2b3ffac96..4765872805 100644 --- a/Library/Homebrew/download_strategy/git_download_strategy.rb +++ b/Library/Homebrew/download_strategy/git_download_strategy.rb @@ -49,6 +49,30 @@ class GitDownloadStrategy < VCSDownloadStrategy @last_commit || "" end + sig { returns(T::Boolean) } + def ref? + silent_command("git", + args: ["--git-dir", git_dir, "rev-parse", "-q", "--verify", "--end-of-options", + "#{@ref}^{commit}"]) + .success? + end + + sig { returns(T::Array[String]) } + def clone_args + args = %w[clone] + + case @ref_type + when :branch, :tag + args << "--branch" << @ref + end + + args << "--no-checkout" << "--filter=blob:none" if partial_clone_sparse_checkout? + + args << "--config" << "advice.detachedHead=false" # Silences “detached head” warning. + args << "--config" << "core.fsmonitor=false" # Prevent `fsmonitor` from watching this repository. + args << "--end-of-options" << @url << cached_location.to_s + end + private # Read user Git config so credential helpers work for private downloads, @@ -101,14 +125,6 @@ class GitDownloadStrategy < VCSDownloadStrategy cached_location/".git" end - sig { returns(T::Boolean) } - def ref? - silent_command("git", - args: ["--git-dir", git_dir, "rev-parse", "-q", "--verify", "--end-of-options", - "#{@ref}^{commit}"]) - .success? - end - sig { override.returns(String) } def current_revision system_command("git", args: ["--git-dir", git_dir, "rev-parse", "-q", "--verify", "HEAD"], @@ -133,22 +149,6 @@ class GitDownloadStrategy < VCSDownloadStrategy Utils::Git.supports_partial_clone_sparse_checkout? end - sig { returns(T::Array[String]) } - def clone_args - args = %w[clone] - - case @ref_type - when :branch, :tag - args << "--branch" << @ref - end - - args << "--no-checkout" << "--filter=blob:none" if partial_clone_sparse_checkout? - - args << "--config" << "advice.detachedHead=false" # Silences “detached head” warning. - args << "--config" << "core.fsmonitor=false" # Prevent `fsmonitor` from watching this repository. - args << "--end-of-options" << @url << cached_location.to_s - end - sig { returns(String) } def refspec case @ref_type diff --git a/Library/Homebrew/download_strategy/github_git_download_strategy.rb b/Library/Homebrew/download_strategy/github_git_download_strategy.rb index b4a50193a5..b78315f9e8 100644 --- a/Library/Homebrew/download_strategy/github_git_download_strategy.rb +++ b/Library/Homebrew/download_strategy/github_git_download_strategy.rb @@ -5,6 +5,12 @@ # # @api public class GitHubGitDownloadStrategy < GitDownloadStrategy + sig { returns(T.nilable(String)) } + attr_reader :user + + sig { returns(T.nilable(String)) } + attr_reader :repo + sig { params(url: String, name: String, version: T.nilable(Version), meta: T.untyped).void } def initialize(url, name, version, **meta) super diff --git a/Library/Homebrew/downloadable.rb b/Library/Homebrew/downloadable.rb index 2a44575d39..9d45e3722e 100644 --- a/Library/Homebrew/downloadable.rb +++ b/Library/Homebrew/downloadable.rb @@ -187,8 +187,9 @@ module Downloadable download_strategy.new(primary_url, download_name, version, mirrors:, cache:, **T.must(@url).specs).tap do |downloader| - if AbstractDownloadStrategy.expand_deferred_environment_for?(downloader) - downloader.send(:allow_deferred_environment_expansion!) + if downloader.is_a?(CurlDownloadStrategy) && + AbstractDownloadStrategy.expand_deferred_environment_for?(downloader) + downloader.allow_deferred_environment_expansion! end end end diff --git a/Library/Homebrew/extend/os/linux/dependency_collector.rb b/Library/Homebrew/extend/os/linux/dependency_collector.rb index e9f5eb6513..ba8494d2b3 100644 --- a/Library/Homebrew/extend/os/linux/dependency_collector.rb +++ b/Library/Homebrew/extend/os/linux/dependency_collector.rb @@ -41,6 +41,11 @@ module OS Dependency.new(GLIBC, [:implicit]) end + sig { returns(T::Hash[String, T::Set[String]]) } + def global_dep_tree + @@global_dep_tree + end + private GLIBC = "glibc" @@ -114,11 +119,6 @@ module OS @@global_dep_tree = T.let({}, T::Hash[String, T::Set[String]]) @@building_global_dep_tree = T.let(false, T::Boolean) - sig { returns(T::Hash[String, T::Set[String]]) } - def global_dep_tree - @@global_dep_tree - end - sig { void } def building_global_dep_tree! @@building_global_dep_tree = true diff --git a/Library/Homebrew/extend/os/linux/dev-cmd/tests.rb b/Library/Homebrew/extend/os/linux/dev-cmd/tests.rb index 132a761a4c..97b0962708 100644 --- a/Library/Homebrew/extend/os/linux/dev-cmd/tests.rb +++ b/Library/Homebrew/extend/os/linux/dev-cmd/tests.rb @@ -11,18 +11,6 @@ module OS requires_ancestor { Homebrew::DevCmd::Tests } - private - - sig { params(bundle_args: T::Array[String]).returns(T::Array[String]) } - def os_bundle_args(bundle_args) - non_macos_bundle_args(bundle_args) - end - - sig { params(files: T::Array[String]).returns(T::Array[String]) } - def os_files(files) - non_macos_files(files) - end - sig { void } def check_test_environment! super @@ -37,6 +25,18 @@ module OS end ::Sandbox.ensure_sandbox_available! end + + private + + sig { params(bundle_args: T::Array[String]).returns(T::Array[String]) } + def os_bundle_args(bundle_args) + non_macos_bundle_args(bundle_args) + end + + sig { params(files: T::Array[String]).returns(T::Array[String]) } + def os_files(files) + non_macos_files(files) + end end end end diff --git a/Library/Homebrew/extend/os/linux/sandbox.rb b/Library/Homebrew/extend/os/linux/sandbox.rb index 6216a189cd..a72502550c 100644 --- a/Library/Homebrew/extend/os/linux/sandbox.rb +++ b/Library/Homebrew/extend/os/linux/sandbox.rb @@ -176,13 +176,6 @@ module OS implementation.run { super } end - private - - sig { params(args: T::Array[T.any(String, ::Pathname)], tmpdir: String).returns(T::Array[T.any(String, ::Pathname)]) } - def sandbox_command(args, tmpdir) - implementation.command(args, tmpdir) - end - sig { params(tmpdir: String).returns(T::Array[String]) } def bubblewrap_args(tmpdir) bubblewrap.arguments(tmpdir) @@ -193,6 +186,13 @@ module OS bubblewrap.writable_paths end + private + + sig { params(args: T::Array[T.any(String, ::Pathname)], tmpdir: String).returns(T::Array[T.any(String, ::Pathname)]) } + def sandbox_command(args, tmpdir) + implementation.command(args, tmpdir) + end + sig { void } def apply_sandbox sandbox = implementation diff --git a/Library/Homebrew/extend/os/linux/sandbox/landlock.rb b/Library/Homebrew/extend/os/linux/sandbox/landlock.rb index b6dfab5c2f..e108c2f13c 100644 --- a/Library/Homebrew/extend/os/linux/sandbox/landlock.rb +++ b/Library/Homebrew/extend/os/linux/sandbox/landlock.rb @@ -369,8 +369,6 @@ class Sandbox end end - private - sig { params(abi: Integer).returns([String, Integer, Integer]) } def ruleset_attributes(abi) allowed_access_fs = WRITE_ACCESS_FS @@ -397,6 +395,17 @@ class Sandbox [attributes.pack("Q*"), handled_access_fs, allowed_access_fs] end + sig { params(denied_paths: T::Array[::Pathname]).returns(T::Array[String]) } + def readable_paths(denied_paths) + return [] if denied_paths.empty? || denied_paths.include?(root_path) + + root_path.children.sort.each_with_object([]) do |path, paths| + add_readable_path(path, denied_paths, paths) + end + end + + private + sig { params(ruleset_fd: Integer, path: String, allowed_access: Integer).void } def add_path_rule(ruleset_fd, path, allowed_access) path_fd = open_path(path) @@ -424,15 +433,6 @@ class Sandbox end end - sig { params(denied_paths: T::Array[::Pathname]).returns(T::Array[String]) } - def readable_paths(denied_paths) - return [] if denied_paths.empty? || denied_paths.include?(root_path) - - root_path.children.sort.each_with_object([]) do |path, paths| - add_readable_path(path, denied_paths, paths) - end - end - sig { params(path: ::Pathname, denied_paths: T::Array[::Pathname], paths: T::Array[String]).void } def add_readable_path(path, denied_paths, paths) return if denied_paths.include?(path) diff --git a/Library/Homebrew/extend/os/mac/utils/bottles.rb b/Library/Homebrew/extend/os/mac/utils/bottles.rb index f982a4dca3..88f12b2a3f 100644 --- a/Library/Homebrew/extend/os/mac/utils/bottles.rb +++ b/Library/Homebrew/extend/os/mac/utils/bottles.rb @@ -20,8 +20,6 @@ module OS requires_ancestor { Utils::Bottles::Collector } - private - sig { params(tag: Utils::Bottles::Tag, no_older_versions: T::Boolean).returns(T.nilable(Utils::Bottles::Tag)) @@ -38,6 +36,8 @@ module OS end end + private + # Find a bottle built for a previous version of macOS. sig { params(tag: Utils::Bottles::Tag).returns(T.nilable(Utils::Bottles::Tag)) } def find_older_compatible_tag(tag) diff --git a/Library/Homebrew/formula.rb b/Library/Homebrew/formula.rb index 44dbd75ccd..8dbe43de63 100644 --- a/Library/Homebrew/formula.rb +++ b/Library/Homebrew/formula.rb @@ -150,7 +150,7 @@ class Formula # # @api public sig { returns(T.nilable(Tap)) } - attr_reader :tap + attr_accessor :tap # The stable (and default) {SoftwareSpec} for this {Formula}. # This contains all the attributes (e.g. URL, checksum) that apply to the @@ -176,8 +176,6 @@ class Formula sig { returns(SoftwareSpec) } attr_reader :active_spec - protected :active_spec - # A symbol to indicate currently active {SoftwareSpec}. # It's either `:stable` or `:head`. # @see #active_spec @@ -215,7 +213,7 @@ class Formula # # @api public sig { returns(T.nilable(Pathname)) } - attr_reader :buildpath + attr_accessor :buildpath # The current working directory during tests. # Will only be non-`nil` inside {.test}. @@ -882,6 +880,9 @@ class Formula end end + sig { params(oldnames: T.nilable(T::Array[String])).void } + attr_writer :oldnames + # All aliases for the formula. # # @api internal @@ -3669,6 +3670,27 @@ class Formula T.must(bottle).tab_attributes end + # Common environment variables used by sandboxed build, test and postinstall phases. + sig { params(home: Pathname).returns(T::Hash[Symbol, String]) } + def common_sandbox_env(home) + { + _JAVA_OPTIONS: "-Duser.home=#{HOMEBREW_CACHE}/java_cache", + GOCACHE: "#{HOMEBREW_CACHE}/go_cache", + GIT_CONFIG_GLOBAL: Utils::Git.no_global_config_file, + GIT_TERMINAL_PROMPT: "0", + GOENV: "off", + GOPATH: "#{HOMEBREW_CACHE}/go_mod_cache", + CARGO_HOME: "#{HOMEBREW_CACHE}/cargo_cache", + BUNDLE_COOLDOWN: Homebrew::RELEASE_COOLDOWN_DAYS.to_s, + PIP_CACHE_DIR: "#{HOMEBREW_CACHE}/pip_cache", + PIP_CONFIG_FILE: File::NULL, + NPM_CONFIG_USERCONFIG: File::NULL, + CURL_HOME: ENV.fetch("CURL_HOME") { home.to_s }, + PYTHONDONTWRITEBYTECODE: "1", + XDG_CONFIG_HOME: "#{home}/.config", + } + end + private sig { void } @@ -3715,27 +3737,6 @@ class Formula exit! 1 # never gets here unless exec threw or failed end - # Common environment variables used by sandboxed build, test and postinstall phases. - sig { params(home: Pathname).returns(T::Hash[Symbol, String]) } - def common_sandbox_env(home) - { - _JAVA_OPTIONS: "-Duser.home=#{HOMEBREW_CACHE}/java_cache", - GOCACHE: "#{HOMEBREW_CACHE}/go_cache", - GIT_CONFIG_GLOBAL: Utils::Git.no_global_config_file, - GIT_TERMINAL_PROMPT: "0", - GOENV: "off", - GOPATH: "#{HOMEBREW_CACHE}/go_mod_cache", - CARGO_HOME: "#{HOMEBREW_CACHE}/cargo_cache", - BUNDLE_COOLDOWN: Homebrew::RELEASE_COOLDOWN_DAYS.to_s, - PIP_CACHE_DIR: "#{HOMEBREW_CACHE}/pip_cache", - PIP_CONFIG_FILE: File::NULL, - NPM_CONFIG_USERCONFIG: File::NULL, - CURL_HOME: ENV.fetch("CURL_HOME") { home.to_s }, - PYTHONDONTWRITEBYTECODE: "1", - XDG_CONFIG_HOME: "#{home}/.config", - } - end - sig { params(interactive: T::Boolean, debug_symbols: T::Boolean, _block: T.proc.params(arg0: Mktemp).void).void } def stage(interactive: false, debug_symbols: false, &_block) active_spec.stage(debug_symbols:) do |staging| diff --git a/Library/Homebrew/formula_auditor.rb b/Library/Homebrew/formula_auditor.rb index c159ea31c5..39c36baed5 100644 --- a/Library/Homebrew/formula_auditor.rb +++ b/Library/Homebrew/formula_auditor.rb @@ -1236,57 +1236,6 @@ module Homebrew end end - private - - sig { params(message: String, location: T.nilable(Homebrew::SourceLocation), corrected: T::Boolean).void } - def problem(message, location: nil, corrected: false) - @problems << ({ message:, location:, corrected: }) - end - - sig { params(message: String, location: T.nilable(Homebrew::SourceLocation), corrected: T::Boolean).void } - def new_formula_problem(message, location: nil, corrected: false) - @new_formula_problems << ({ message:, location:, corrected: }) - end - - sig { params(repo_owner: String).returns(T::Boolean) } - def self_submission?(repo_owner) - return false if repo_owner.blank? - - SharedAudits.self_submission_for_repo_owner?(repo_owner) - end - - sig { params(formula: Formula).returns(T::Boolean) } - def head_only?(formula) - !!formula.head && formula.stable.nil? - end - - sig { params(formula: Formula).returns(T::Boolean) } - def linux_only_gcc_dep?(formula) - odie "`#linux_only_gcc_dep?` works only on Linux!" if Homebrew::SimulateSystem.simulating_or_running_on_macos? - return false if formula.deps.none? { |dep| dep.name == "gcc" && !dep.implicit? } - - variations = formula.to_hash_with_variations["variations"] - # The formula has no variations, so all OS-version-arch triples depend on GCC. - return false if variations.blank? - - MacOSVersion::SYMBOLS.keys.product(OnSystem::ARCH_OPTIONS).each do |os, arch| - bottle_tag = Utils::Bottles::Tag.new(system: os, arch:) - next unless bottle_tag.valid_combination? - - variation_dependencies = variations.dig(bottle_tag.to_sym, "dependencies") - # This variation either: - # 1. does not exist - # 2. has no variation-specific dependencies - # In either case, it matches Linux. We must check for `nil` because an empty - # array indicates that this variation does not depend on GCC. - return false if variation_dependencies.nil? - # We found a non-Linux variation that depends on GCC. - return false if variation_dependencies.include?("gcc") - end - - true - end - sig { params(tap: Tap, only_names: T::Array[String]).returns(T::Array[Pathname]) } def changed_formulae_paths(tap, only_names: [].freeze) return [] unless tap.git? @@ -1369,6 +1318,57 @@ module Homebrew @committed_version_info_cache[formula.full_name] = [previous_version_info, base_ref_version_info] end + private + + sig { params(message: String, location: T.nilable(Homebrew::SourceLocation), corrected: T::Boolean).void } + def problem(message, location: nil, corrected: false) + @problems << ({ message:, location:, corrected: }) + end + + sig { params(message: String, location: T.nilable(Homebrew::SourceLocation), corrected: T::Boolean).void } + def new_formula_problem(message, location: nil, corrected: false) + @new_formula_problems << ({ message:, location:, corrected: }) + end + + sig { params(repo_owner: String).returns(T::Boolean) } + def self_submission?(repo_owner) + return false if repo_owner.blank? + + SharedAudits.self_submission_for_repo_owner?(repo_owner) + end + + sig { params(formula: Formula).returns(T::Boolean) } + def head_only?(formula) + !!formula.head && formula.stable.nil? + end + + sig { params(formula: Formula).returns(T::Boolean) } + def linux_only_gcc_dep?(formula) + odie "`#linux_only_gcc_dep?` works only on Linux!" if Homebrew::SimulateSystem.simulating_or_running_on_macos? + return false if formula.deps.none? { |dep| dep.name == "gcc" && !dep.implicit? } + + variations = formula.to_hash_with_variations["variations"] + # The formula has no variations, so all OS-version-arch triples depend on GCC. + return false if variations.blank? + + MacOSVersion::SYMBOLS.keys.product(OnSystem::ARCH_OPTIONS).each do |os, arch| + bottle_tag = Utils::Bottles::Tag.new(system: os, arch:) + next unless bottle_tag.valid_combination? + + variation_dependencies = variations.dig(bottle_tag.to_sym, "dependencies") + # This variation either: + # 1. does not exist + # 2. has no variation-specific dependencies + # In either case, it matches Linux. We must check for `nil` because an empty + # array indicates that this variation does not depend on GCC. + return false if variation_dependencies.nil? + # We found a non-Linux variation that depends on GCC. + return false if variation_dependencies.include?("gcc") + end + + true + end + sig { params(tap: Tap).returns(String) } def git_audit_base_ref(tap) @git_audit_base_ref_cache ||= T.let({}, T.nilable(T::Hash[Pathname, T.nilable(String)])) diff --git a/Library/Homebrew/formula_installer.rb b/Library/Homebrew/formula_installer.rb index cb5548126b..fd5240160c 100644 --- a/Library/Homebrew/formula_installer.rb +++ b/Library/Homebrew/formula_installer.rb @@ -49,6 +49,9 @@ class FormulaInstaller sig { returns(Homebrew::DownloadQueue) } attr_accessor :download_queue + sig { params(ran_prelude: T::Boolean).void } + attr_writer :ran_prelude + sig { params( formula: Formula, diff --git a/Library/Homebrew/github_packages.rb b/Library/Homebrew/github_packages.rb index 89f1b7c632..ba2c6e609b 100644 --- a/Library/Homebrew/github_packages.rb +++ b/Library/Homebrew/github_packages.rb @@ -141,149 +141,6 @@ class GitHubPackages version_rebuild end - private - - IMAGE_CONFIG_SCHEMA_URI = "https://opencontainers.org/schema/image/config" - IMAGE_INDEX_SCHEMA_URI = "https://opencontainers.org/schema/image/index" - IMAGE_LAYOUT_SCHEMA_URI = "https://opencontainers.org/schema/image/layout" - IMAGE_MANIFEST_SCHEMA_URI = "https://opencontainers.org/schema/image/manifest" - - GITHUB_PACKAGE_TYPE = "homebrew_bottle" - private_constant :IMAGE_CONFIG_SCHEMA_URI, :IMAGE_INDEX_SCHEMA_URI, :IMAGE_LAYOUT_SCHEMA_URI, - :IMAGE_MANIFEST_SCHEMA_URI, :GITHUB_PACKAGE_TYPE - - sig { void } - def load_schemas! - schema_uri("content-descriptor", - "https://opencontainers.org/schema/image/content-descriptor.json") - schema_uri("defs", %w[ - https://opencontainers.org/schema/defs.json - https://opencontainers.org/schema/descriptor/defs.json - https://opencontainers.org/schema/image/defs.json - https://opencontainers.org/schema/image/descriptor/defs.json - https://opencontainers.org/schema/image/index/defs.json - https://opencontainers.org/schema/image/manifest/defs.json - ]) - schema_uri("defs-descriptor", %w[ - https://opencontainers.org/schema/descriptor.json - https://opencontainers.org/schema/defs-descriptor.json - https://opencontainers.org/schema/descriptor/defs-descriptor.json - https://opencontainers.org/schema/image/defs-descriptor.json - https://opencontainers.org/schema/image/descriptor/defs-descriptor.json - https://opencontainers.org/schema/image/index/defs-descriptor.json - https://opencontainers.org/schema/image/manifest/defs-descriptor.json - https://opencontainers.org/schema/index/defs-descriptor.json - ]) - schema_uri("config-schema", IMAGE_CONFIG_SCHEMA_URI) - schema_uri("image-index-schema", IMAGE_INDEX_SCHEMA_URI) - schema_uri("image-layout-schema", IMAGE_LAYOUT_SCHEMA_URI) - schema_uri("image-manifest-schema", IMAGE_MANIFEST_SCHEMA_URI) - end - - sig { params(basename: String, uris: T.any(String, T::Array[String])).void } - def schema_uri(basename, uris) - # The current `main` version has an invalid JSON schema. - # Going forward, this should probably be pinned to tags. - # We currently use features newer than the last one (v1.0.2). - url = "https://raw.githubusercontent.com/opencontainers/image-spec/170393e57ed656f7f81c3070bfa8c3346eaa0a5a/schema/#{basename}.json" - out = Utils::Curl.curl_output(url).stdout - json = JSON.parse(out) - - @schema_json ||= T.let({}, T.nilable(T::Hash[String, T::Hash[String, T.untyped]])) - Array(uris).each do |uri| - @schema_json[uri] = json - end - end - - T::Sig::WithoutRuntime.sig { params(uri: URI::Generic).returns(T.nilable(T::Hash[String, T.untyped])) } - def schema_resolver(uri) - @schema_json&.fetch(uri.to_s.gsub(/#.*/, "")) - end - - sig { params(schema_uri: String, json: T::Hash[T.any(String, Symbol), T.untyped]).void } - def validate_schema!(schema_uri, json) - schema = JSONSchemer.schema(@schema_json&.fetch(schema_uri), ref_resolver: method(:schema_resolver)) - json = json.deep_stringify_keys - return if schema.valid?(json) - - puts - ofail "#{Formatter.url(schema_uri)} JSON schema validation failed!" - oh1 "Errors" - puts schema.validate(json).to_a.inspect - oh1 "JSON" - puts json.inspect - exit 1 - end - - sig { params(user: String, token: String, skopeo: Pathname, image_uri: String, root: Pathname, dry_run: T::Boolean).void } - def download(user, token, skopeo, image_uri, root, dry_run:) - puts - args = ["copy", "--all", image_uri.to_s, "oci:#{root}"] - if dry_run - puts "#{skopeo} #{args.join(" ")} --src-creds=#{user}:$HOMEBREW_GITHUB_PACKAGES_TOKEN" - else - args << "--src-creds=#{user}:#{token}" - system_command!(skopeo, verbose: true, print_stdout: true, args:) - end - end - - sig { - params( - user: String, token: String, skopeo: Pathname, _formula_full_name: String, - bottle_hash: T::Hash[String, T.untyped], keep_old: T::Boolean, dry_run: T::Boolean, warn_on_error: T::Boolean - ).returns( - T.nilable([String, String, String, Version, Integer, String, String, String, T::Boolean]), - ) - } - def preupload_check(user, token, skopeo, _formula_full_name, bottle_hash, keep_old:, dry_run:, warn_on_error:) - formula_name = bottle_hash["formula"]["name"] - - _, org, repo, = *bottle_hash["bottle"]["root_url"].match(URL_REGEX) - repo = "homebrew-#{repo}" unless repo.start_with?("homebrew-") - - version = Version.new(bottle_hash["formula"]["pkg_version"]) - rebuild = bottle_hash["bottle"]["rebuild"].to_i - version_rebuild = GitHubPackages.version_rebuild(version, rebuild) - - image_name = GitHubPackages.image_formula_name(formula_name) - image_tag = GitHubPackages.image_version_rebuild(version_rebuild) - image_uri = "#{GitHubPackages.root_url(org, repo, DOCKER_PREFIX)}/#{image_name}:#{image_tag}" - - puts - inspect_args = ["inspect", "--raw", image_uri.to_s] - if dry_run - puts "#{skopeo} #{inspect_args.join(" ")} --creds=#{user}:$HOMEBREW_GITHUB_PACKAGES_TOKEN" - else - inspect_args << "--creds=#{user}:#{token}" - inspect_result = system_command(skopeo, print_stderr: false, args: inspect_args) - - # Order here is important. - if !inspect_result.status.success? && !inspect_result.stderr.match?(/(name|manifest) unknown/) - # We got an error and it was not about the tag or package being unknown. - if warn_on_error - opoo "#{image_uri} inspection returned an error, skipping upload!\n#{inspect_result.stderr}" - return - else - odie "#{image_uri} inspection returned an error!\n#{inspect_result.stderr}" - end - elsif keep_old - # If the tag doesn't exist, ignore `--keep-old`. - keep_old = false unless inspect_result.status.success? - # Otherwise, do nothing - the tag already existing is expected behaviour for --keep-old. - elsif inspect_result.status.success? - # The tag already exists and we are not passing `--keep-old`. - if warn_on_error - opoo "#{image_uri} already exists, skipping upload!" - return - else - odie "#{image_uri} already exists!" - end - end - end - - [formula_name, org, repo, version, rebuild, version_rebuild, image_name, image_uri, keep_old] - end - sig { params( user: String, token: String, skopeo: Pathname, formula_full_name: String, @@ -518,6 +375,149 @@ class GitHubPackages end end + private + + IMAGE_CONFIG_SCHEMA_URI = "https://opencontainers.org/schema/image/config" + IMAGE_INDEX_SCHEMA_URI = "https://opencontainers.org/schema/image/index" + IMAGE_LAYOUT_SCHEMA_URI = "https://opencontainers.org/schema/image/layout" + IMAGE_MANIFEST_SCHEMA_URI = "https://opencontainers.org/schema/image/manifest" + + GITHUB_PACKAGE_TYPE = "homebrew_bottle" + private_constant :IMAGE_CONFIG_SCHEMA_URI, :IMAGE_INDEX_SCHEMA_URI, :IMAGE_LAYOUT_SCHEMA_URI, + :IMAGE_MANIFEST_SCHEMA_URI, :GITHUB_PACKAGE_TYPE + + sig { void } + def load_schemas! + schema_uri("content-descriptor", + "https://opencontainers.org/schema/image/content-descriptor.json") + schema_uri("defs", %w[ + https://opencontainers.org/schema/defs.json + https://opencontainers.org/schema/descriptor/defs.json + https://opencontainers.org/schema/image/defs.json + https://opencontainers.org/schema/image/descriptor/defs.json + https://opencontainers.org/schema/image/index/defs.json + https://opencontainers.org/schema/image/manifest/defs.json + ]) + schema_uri("defs-descriptor", %w[ + https://opencontainers.org/schema/descriptor.json + https://opencontainers.org/schema/defs-descriptor.json + https://opencontainers.org/schema/descriptor/defs-descriptor.json + https://opencontainers.org/schema/image/defs-descriptor.json + https://opencontainers.org/schema/image/descriptor/defs-descriptor.json + https://opencontainers.org/schema/image/index/defs-descriptor.json + https://opencontainers.org/schema/image/manifest/defs-descriptor.json + https://opencontainers.org/schema/index/defs-descriptor.json + ]) + schema_uri("config-schema", IMAGE_CONFIG_SCHEMA_URI) + schema_uri("image-index-schema", IMAGE_INDEX_SCHEMA_URI) + schema_uri("image-layout-schema", IMAGE_LAYOUT_SCHEMA_URI) + schema_uri("image-manifest-schema", IMAGE_MANIFEST_SCHEMA_URI) + end + + sig { params(basename: String, uris: T.any(String, T::Array[String])).void } + def schema_uri(basename, uris) + # The current `main` version has an invalid JSON schema. + # Going forward, this should probably be pinned to tags. + # We currently use features newer than the last one (v1.0.2). + url = "https://raw.githubusercontent.com/opencontainers/image-spec/170393e57ed656f7f81c3070bfa8c3346eaa0a5a/schema/#{basename}.json" + out = Utils::Curl.curl_output(url).stdout + json = JSON.parse(out) + + @schema_json ||= T.let({}, T.nilable(T::Hash[String, T::Hash[String, T.untyped]])) + Array(uris).each do |uri| + @schema_json[uri] = json + end + end + + T::Sig::WithoutRuntime.sig { params(uri: URI::Generic).returns(T.nilable(T::Hash[String, T.untyped])) } + def schema_resolver(uri) + @schema_json&.fetch(uri.to_s.gsub(/#.*/, "")) + end + + sig { params(schema_uri: String, json: T::Hash[T.any(String, Symbol), T.untyped]).void } + def validate_schema!(schema_uri, json) + schema = JSONSchemer.schema(@schema_json&.fetch(schema_uri), ref_resolver: method(:schema_resolver)) + json = json.deep_stringify_keys + return if schema.valid?(json) + + puts + ofail "#{Formatter.url(schema_uri)} JSON schema validation failed!" + oh1 "Errors" + puts schema.validate(json).to_a.inspect + oh1 "JSON" + puts json.inspect + exit 1 + end + + sig { params(user: String, token: String, skopeo: Pathname, image_uri: String, root: Pathname, dry_run: T::Boolean).void } + def download(user, token, skopeo, image_uri, root, dry_run:) + puts + args = ["copy", "--all", image_uri.to_s, "oci:#{root}"] + if dry_run + puts "#{skopeo} #{args.join(" ")} --src-creds=#{user}:$HOMEBREW_GITHUB_PACKAGES_TOKEN" + else + args << "--src-creds=#{user}:#{token}" + system_command!(skopeo, verbose: true, print_stdout: true, args:) + end + end + + sig { + params( + user: String, token: String, skopeo: Pathname, _formula_full_name: String, + bottle_hash: T::Hash[String, T.untyped], keep_old: T::Boolean, dry_run: T::Boolean, warn_on_error: T::Boolean + ).returns( + T.nilable([String, String, String, Version, Integer, String, String, String, T::Boolean]), + ) + } + def preupload_check(user, token, skopeo, _formula_full_name, bottle_hash, keep_old:, dry_run:, warn_on_error:) + formula_name = bottle_hash["formula"]["name"] + + _, org, repo, = *bottle_hash["bottle"]["root_url"].match(URL_REGEX) + repo = "homebrew-#{repo}" unless repo.start_with?("homebrew-") + + version = Version.new(bottle_hash["formula"]["pkg_version"]) + rebuild = bottle_hash["bottle"]["rebuild"].to_i + version_rebuild = GitHubPackages.version_rebuild(version, rebuild) + + image_name = GitHubPackages.image_formula_name(formula_name) + image_tag = GitHubPackages.image_version_rebuild(version_rebuild) + image_uri = "#{GitHubPackages.root_url(org, repo, DOCKER_PREFIX)}/#{image_name}:#{image_tag}" + + puts + inspect_args = ["inspect", "--raw", image_uri.to_s] + if dry_run + puts "#{skopeo} #{inspect_args.join(" ")} --creds=#{user}:$HOMEBREW_GITHUB_PACKAGES_TOKEN" + else + inspect_args << "--creds=#{user}:#{token}" + inspect_result = system_command(skopeo, print_stderr: false, args: inspect_args) + + # Order here is important. + if !inspect_result.status.success? && !inspect_result.stderr.match?(/(name|manifest) unknown/) + # We got an error and it was not about the tag or package being unknown. + if warn_on_error + opoo "#{image_uri} inspection returned an error, skipping upload!\n#{inspect_result.stderr}" + return + else + odie "#{image_uri} inspection returned an error!\n#{inspect_result.stderr}" + end + elsif keep_old + # If the tag doesn't exist, ignore `--keep-old`. + keep_old = false unless inspect_result.status.success? + # Otherwise, do nothing - the tag already existing is expected behaviour for --keep-old. + elsif inspect_result.status.success? + # The tag already exists and we are not passing `--keep-old`. + if warn_on_error + opoo "#{image_uri} already exists, skipping upload!" + return + else + odie "#{image_uri} already exists!" + end + end + end + + [formula_name, org, repo, version, rebuild, version_rebuild, image_name, image_uri, keep_old] + end + sig { params(root: Pathname).returns([String, Integer]) } def write_image_layout(root) image_layout = { imageLayoutVersion: "1.0.0" } diff --git a/Library/Homebrew/github_runner_matrix.rb b/Library/Homebrew/github_runner_matrix.rb index b322eb18a9..84150e994d 100644 --- a/Library/Homebrew/github_runner_matrix.rb +++ b/Library/Homebrew/github_runner_matrix.rb @@ -108,86 +108,6 @@ class GitHubRunnerMatrix end end - private - - # ARM macOS timeout, keep this under 1/2 of GitHub's job execution time limit for self-hosted runners. - # https://docs.github.com/en/actions/hosting-your-own-runners/managing-self-hosted-runners/about-self-hosted-runners#usage-limits - GITHUB_ACTIONS_LONG_TIMEOUT = 2160 # 36 hours - GITHUB_ACTIONS_SHORT_TIMEOUT = 60 - private_constant :GITHUB_ACTIONS_LONG_TIMEOUT, :GITHUB_ACTIONS_SHORT_TIMEOUT - - sig { params(arch: Symbol, self_hosted: T::Boolean).returns(LinuxRunnerSpec) } - def linux_runner_spec(arch, self_hosted:) - linux_runner = case arch - when :arm64 then self_hosted ? "linux-arm64#{ephemeral_suffix}" : OS::LINUX_CI_ARM_RUNNER - when :x86_64 then self_hosted ? "linux-x86_64#{ephemeral_suffix}" : "ubuntu-latest" - else raise "Unknown Linux architecture: #{arch}" - end - - unless self_hosted - container = { - image: "ghcr.io/homebrew/brew:main", - options: "--user linuxbrew --env HOMEBREW_SANDBOX_LINUX_LANDLOCK=1", - } - workdir = "/github/home" - end - - LinuxRunnerSpec.new( - name: "Linux #{arch}", - runner: linux_runner, - container:, - workdir:, - timeout: GITHUB_ACTIONS_LONG_TIMEOUT, - cleanup: false, - ) - end - - VALID_PLATFORMS = [:macos, :linux].freeze - VALID_ARCHES = [:arm64, :x86_64].freeze - private_constant :VALID_PLATFORMS, :VALID_ARCHES - - sig { - params( - platform: Symbol, - arch: Symbol, - spec: RunnerSpec, - macos_version: T.nilable(MacOSVersion), - ).returns(GitHubRunner) - } - def create_runner(platform, arch, spec, macos_version = nil) - raise "Unexpected platform: #{platform}" if VALID_PLATFORMS.exclude?(platform) - raise "Unexpected arch: #{arch}" if VALID_ARCHES.exclude?(arch) - - runner = GitHubRunner.new(platform:, arch:, spec:, macos_version:) - runner.spec.testing_formulae += testable_formulae(runner) - runner.active = active_runner?(runner) - runner.freeze - end - - sig { params(macos_version: MacOSVersion).returns(T::Boolean) } - def runner_enabled?(macos_version) - macos_version.between?(OLDEST_HOMEBREW_CORE_MACOS_RUNNER, NEWEST_HOMEBREW_CORE_MACOS_RUNNER) - end - - sig { returns(String) } - def ephemeral_suffix - @ephemeral_suffix ||= T.let(begin - suffix = "-#{@github_run_id}" - suffix << "-deps" if @dependent_matrix - suffix << "-long" if @runner_timeout == GITHUB_ACTIONS_LONG_TIMEOUT - suffix.freeze - end, T.nilable(String)) - end - - NEWEST_GITHUB_ACTIONS_INTEL_MACOS_RUNNER = :ventura - OLDEST_GITHUB_ACTIONS_INTEL_MACOS_RUNNER = :ventura - NEWEST_GITHUB_ACTIONS_ARM_MACOS_RUNNER = :tahoe - OLDEST_GITHUB_ACTIONS_ARM_MACOS_RUNNER = :sonoma - GITHUB_ACTIONS_RUNNER_TIMEOUT = 360 - private_constant :NEWEST_GITHUB_ACTIONS_INTEL_MACOS_RUNNER, :OLDEST_GITHUB_ACTIONS_INTEL_MACOS_RUNNER, - :NEWEST_GITHUB_ACTIONS_ARM_MACOS_RUNNER, :OLDEST_GITHUB_ACTIONS_ARM_MACOS_RUNNER, - :GITHUB_ACTIONS_RUNNER_TIMEOUT - sig { void } def generate_runners! return if @runners.present? @@ -284,6 +204,86 @@ class GitHubRunnerMatrix @runners.freeze end + private + + # ARM macOS timeout, keep this under 1/2 of GitHub's job execution time limit for self-hosted runners. + # https://docs.github.com/en/actions/hosting-your-own-runners/managing-self-hosted-runners/about-self-hosted-runners#usage-limits + GITHUB_ACTIONS_LONG_TIMEOUT = 2160 # 36 hours + GITHUB_ACTIONS_SHORT_TIMEOUT = 60 + private_constant :GITHUB_ACTIONS_LONG_TIMEOUT, :GITHUB_ACTIONS_SHORT_TIMEOUT + + sig { params(arch: Symbol, self_hosted: T::Boolean).returns(LinuxRunnerSpec) } + def linux_runner_spec(arch, self_hosted:) + linux_runner = case arch + when :arm64 then self_hosted ? "linux-arm64#{ephemeral_suffix}" : OS::LINUX_CI_ARM_RUNNER + when :x86_64 then self_hosted ? "linux-x86_64#{ephemeral_suffix}" : "ubuntu-latest" + else raise "Unknown Linux architecture: #{arch}" + end + + unless self_hosted + container = { + image: "ghcr.io/homebrew/brew:main", + options: "--user linuxbrew --env HOMEBREW_SANDBOX_LINUX_LANDLOCK=1", + } + workdir = "/github/home" + end + + LinuxRunnerSpec.new( + name: "Linux #{arch}", + runner: linux_runner, + container:, + workdir:, + timeout: GITHUB_ACTIONS_LONG_TIMEOUT, + cleanup: false, + ) + end + + VALID_PLATFORMS = [:macos, :linux].freeze + VALID_ARCHES = [:arm64, :x86_64].freeze + private_constant :VALID_PLATFORMS, :VALID_ARCHES + + sig { + params( + platform: Symbol, + arch: Symbol, + spec: RunnerSpec, + macos_version: T.nilable(MacOSVersion), + ).returns(GitHubRunner) + } + def create_runner(platform, arch, spec, macos_version = nil) + raise "Unexpected platform: #{platform}" if VALID_PLATFORMS.exclude?(platform) + raise "Unexpected arch: #{arch}" if VALID_ARCHES.exclude?(arch) + + runner = GitHubRunner.new(platform:, arch:, spec:, macos_version:) + runner.spec.testing_formulae += testable_formulae(runner) + runner.active = active_runner?(runner) + runner.freeze + end + + sig { params(macos_version: MacOSVersion).returns(T::Boolean) } + def runner_enabled?(macos_version) + macos_version.between?(OLDEST_HOMEBREW_CORE_MACOS_RUNNER, NEWEST_HOMEBREW_CORE_MACOS_RUNNER) + end + + sig { returns(String) } + def ephemeral_suffix + @ephemeral_suffix ||= T.let(begin + suffix = "-#{@github_run_id}" + suffix << "-deps" if @dependent_matrix + suffix << "-long" if @runner_timeout == GITHUB_ACTIONS_LONG_TIMEOUT + suffix.freeze + end, T.nilable(String)) + end + + NEWEST_GITHUB_ACTIONS_INTEL_MACOS_RUNNER = :ventura + OLDEST_GITHUB_ACTIONS_INTEL_MACOS_RUNNER = :ventura + NEWEST_GITHUB_ACTIONS_ARM_MACOS_RUNNER = :tahoe + OLDEST_GITHUB_ACTIONS_ARM_MACOS_RUNNER = :sonoma + GITHUB_ACTIONS_RUNNER_TIMEOUT = 360 + private_constant :NEWEST_GITHUB_ACTIONS_INTEL_MACOS_RUNNER, :OLDEST_GITHUB_ACTIONS_INTEL_MACOS_RUNNER, + :NEWEST_GITHUB_ACTIONS_ARM_MACOS_RUNNER, :OLDEST_GITHUB_ACTIONS_ARM_MACOS_RUNNER, + :GITHUB_ACTIONS_RUNNER_TIMEOUT + sig { params(runner: GitHubRunner).returns(T::Array[String]) } def testable_formulae(runner) formulae = if @dependent_matrix diff --git a/Library/Homebrew/install.rb b/Library/Homebrew/install.rb index 81b253ed58..97ec4b4902 100644 --- a/Library/Homebrew/install.rb +++ b/Library/Homebrew/install.rb @@ -720,6 +720,16 @@ module Homebrew ask_input(action:) end + sig { params(all_fatal: T::Boolean).void } + def perform_preinstall_checks(all_fatal: false) + check_prefix + check_cpu + attempt_directory_creation + Diagnostic.checks(:supported_configuration_checks, fatal: all_fatal) + Diagnostic.checks(:preinstall_checks, fatal: false) + Diagnostic.checks(:fatal_preinstall_checks) + end + private sig { params(action: String).returns(String) } @@ -741,16 +751,6 @@ module Homebrew .map { |k| Keg.new(k.resolved_path) } end - sig { params(all_fatal: T::Boolean).void } - def perform_preinstall_checks(all_fatal: false) - check_prefix - check_cpu - attempt_directory_creation - Diagnostic.checks(:supported_configuration_checks, fatal: all_fatal) - Diagnostic.checks(:preinstall_checks, fatal: false) - Diagnostic.checks(:fatal_preinstall_checks) - end - sig { void } def attempt_directory_creation Keg.must_exist_directories.each do |dir| diff --git a/Library/Homebrew/language/node.rb b/Library/Homebrew/language/node.rb index 0d5ae785d9..d33d33542d 100644 --- a/Library/Homebrew/language/node.rb +++ b/Library/Homebrew/language/node.rb @@ -12,6 +12,11 @@ module Language module Node extend ::Utils::Output::Mixin + class << self + sig { returns(T.nilable(T::Boolean)) } + attr_accessor :env_set + end + sig { returns(String) } def self.npm_cache_config "cache=#{HOMEBREW_CACHE}/npm_cache" diff --git a/Library/Homebrew/livecheck/livecheck.rb b/Library/Homebrew/livecheck/livecheck.rb index 4f3001c115..e90cb39b96 100644 --- a/Library/Homebrew/livecheck/livecheck.rb +++ b/Library/Homebrew/livecheck/livecheck.rb @@ -34,13 +34,13 @@ module Homebrew private_constant :UNSTABLE_VERSION_KEYWORDS sig { params(strategy_class: T::Class[Strategic]).returns(String) } - private_class_method def self.livecheck_strategy_names(strategy_class) + def self.livecheck_strategy_names(strategy_class) @livecheck_strategy_names ||= T.let({}, T.nilable(T::Hash[T::Class[Strategic], String])) @livecheck_strategy_names[strategy_class] ||= Utils.demodulize(strategy_class.name) end sig { params(strategy_class: T::Class[Strategic]).returns(T::Array[Symbol]) } - private_class_method def self.livecheck_find_versions_parameters(strategy_class) + def self.livecheck_find_versions_parameters(strategy_class) @livecheck_find_versions_parameters ||= T.let({}, T.nilable(T::Hash[T::Class[Strategic], T::Array[Symbol]])) @livecheck_find_versions_parameters[strategy_class] ||= (T::Utils.signature_for_method(strategy_class.method(:find_versions))&.parameters || @@ -1133,7 +1133,7 @@ module Homebrew end sig { params(package_or_resource: T.any(Formula, Cask::Cask)).returns(T.nilable(Integer)) } - private_class_method def self.formula_or_cask_last_updated_timestamp(package_or_resource) + def self.formula_or_cask_last_updated_timestamp(package_or_resource) tap = package_or_resource.tap return if tap.nil? return unless tap.git? @@ -1261,7 +1261,7 @@ module Homebrew end sig { params(package_or_resource: T.any(Formula, Cask::Cask), days: Integer).returns(T::Boolean) } - private_class_method def self.throttle_interval_elapsed?(package_or_resource, days) + def self.throttle_interval_elapsed?(package_or_resource, days) return false if days <= 0 last_updated_timestamp = formula_or_cask_last_updated_timestamp(package_or_resource) diff --git a/Library/Homebrew/livecheck/strategy/git.rb b/Library/Homebrew/livecheck/strategy/git.rb index b8b2df2840..e8de5cea2a 100644 --- a/Library/Homebrew/livecheck/strategy/git.rb +++ b/Library/Homebrew/livecheck/strategy/git.rb @@ -32,6 +32,11 @@ module Homebrew # Used to cache processed URLs, to avoid duplicating effort. @processed_urls = T.let({}, T::Hash[String, String]) + class << self + sig { params(processed_urls: T::Hash[String, String]).void } + attr_writer :processed_urls + end + # The priority of the strategy on an informal scale of 1 to 10 (from # lowest to highest). PRIORITY = 8 diff --git a/Library/Homebrew/livecheck/strategy/xorg.rb b/Library/Homebrew/livecheck/strategy/xorg.rb index a1620f3310..dbb514cf41 100644 --- a/Library/Homebrew/livecheck/strategy/xorg.rb +++ b/Library/Homebrew/livecheck/strategy/xorg.rb @@ -63,6 +63,11 @@ module Homebrew # repeatedly. @page_data = T.let({}, T::Hash[String, String]) + class << self + sig { params(page_data: T::Hash[String, String]).void } + attr_writer :page_data + end + # Whether the strategy can be applied to the provided URL. # # @param url [String] the URL to match against diff --git a/Library/Homebrew/macos_version.rb b/Library/Homebrew/macos_version.rb index 257468a7ab..43af6166c2 100644 --- a/Library/Homebrew/macos_version.rb +++ b/Library/Homebrew/macos_version.rb @@ -54,6 +54,12 @@ class MacOSVersion < Version new(str) end + sig { returns(T::Hash[T.untyped, T.nilable(Integer)]) } + attr_reader :comparison_cache + + sig { returns(T.nilable(Symbol)) } + attr_reader :sym + sig { params(version: T.nilable(String)).void } def initialize(version) raise MacOSVersion::Error, version unless /\A\d{2,}(?:\.\d+){0,2}\z/.match?(version) diff --git a/Library/Homebrew/os/linux/ld.rb b/Library/Homebrew/os/linux/ld.rb index f46e033ffd..25064dc3cb 100644 --- a/Library/Homebrew/os/linux/ld.rb +++ b/Library/Homebrew/os/linux/ld.rb @@ -19,6 +19,11 @@ module OS /system/bin/linker ].freeze + class << self + sig { params(system_ld_so: T.nilable(::Pathname)).returns(T.nilable(::Pathname)) } + attr_writer :system_ld_so + end + # The path to the system's dynamic linker or `nil` if not found sig { returns(T.nilable(::Pathname)) } def self.system_ld_so diff --git a/Library/Homebrew/os/linux/libstdcxx.rb b/Library/Homebrew/os/linux/libstdcxx.rb index a8089cdf60..711e6ddc71 100644 --- a/Library/Homebrew/os/linux/libstdcxx.rb +++ b/Library/Homebrew/os/linux/libstdcxx.rb @@ -10,6 +10,11 @@ module OS SOVERSION = 6 SONAME = T.let("libstdc++.so.#{SOVERSION}".freeze, String) + class << self + sig { params(system_version: T.nilable(Version)).returns(T.nilable(Version)) } + attr_writer :system_version + end + sig { returns(T::Boolean) } def self.below_ci_version? system_version < LINUX_LIBSTDCXX_CI_VERSION diff --git a/Library/Homebrew/reinstall/reinstall.rb b/Library/Homebrew/reinstall/reinstall.rb index 635658e9ac..11295d833a 100644 --- a/Library/Homebrew/reinstall/reinstall.rb +++ b/Library/Homebrew/reinstall/reinstall.rb @@ -116,8 +116,6 @@ module Homebrew nil end - private - sig { params(keg: Keg).void } def backup(keg) keg.unlink @@ -132,6 +130,8 @@ module Homebrew end end + private + sig { params(keg: Keg, keg_was_linked: T::Boolean, verbose: T::Boolean).void } def restore_backup(keg, keg_was_linked, verbose:) path = backup_path(keg) diff --git a/Library/Homebrew/resource.rb b/Library/Homebrew/resource.rb index cd512b6f22..4a13a5c491 100644 --- a/Library/Homebrew/resource.rb +++ b/Library/Homebrew/resource.rb @@ -357,6 +357,9 @@ class Resource sig { returns(Bottle) } attr_reader :bottle + sig { params(manifest_annotations: T.nilable(T::Hash[String, String])).void } + attr_writer :manifest_annotations + sig { params(bottle: Bottle).void } def initialize(bottle) super("#{bottle.name}_bottle_manifest") diff --git a/Library/Homebrew/rubocops/all.rb b/Library/Homebrew/rubocops/all.rb index bdabe62c91..1293e278b3 100644 --- a/Library/Homebrew/rubocops/all.rb +++ b/Library/Homebrew/rubocops/all.rb @@ -14,6 +14,8 @@ require_relative "io_read" require_relative "move_to_extend_os" require_relative "negate_include" require_relative "no_fileutils_rmrf" +require_relative "no_instance_variable_access_in_tests" +require_relative "no_send_in_tests" require_relative "non_public_api_usage" require_relative "presence" require_relative "public_api_cookbook" diff --git a/Library/Homebrew/rubocops/no_instance_variable_access_in_tests.rb b/Library/Homebrew/rubocops/no_instance_variable_access_in_tests.rb new file mode 100644 index 0000000000..02c901b3e7 --- /dev/null +++ b/Library/Homebrew/rubocops/no_instance_variable_access_in_tests.rb @@ -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 " \ + "`%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 diff --git a/Library/Homebrew/rubocops/no_send_in_tests.rb b/Library/Homebrew/rubocops/no_send_in_tests.rb new file mode 100644 index 0000000000..1b35a9499b --- /dev/null +++ b/Library/Homebrew/rubocops/no_send_in_tests.rb @@ -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 `%s` in tests." + MSG_SEND_DYNAMIC = "Use `public_send` instead of `%s` in tests; " \ + "`%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 diff --git a/Library/Homebrew/sandbox.rb b/Library/Homebrew/sandbox.rb index 768543c630..ada62f515d 100644 --- a/Library/Homebrew/sandbox.rb +++ b/Library/Homebrew/sandbox.rb @@ -595,11 +595,19 @@ class Sandbox SandboxPathFilter.new(path: filter_path, type:) end - private - sig { returns(SandboxProfile) } attr_reader :profile + sig { params(controller: IO).void } + def copy_pty_output(controller) + controller.each_char { |c| print(c) } + rescue Errno::EIO + # Linux marks a PTY as an I/O error when its peer closes, so treat this as EOF: + # https://github.com/torvalds/linux/blob/master/drivers/tty/pty.c + end + + private + sig { returns(T::Boolean) } attr_reader :failed @@ -630,14 +638,6 @@ class Sandbox sig { void } def apply_sandbox; end - sig { params(controller: IO).void } - def copy_pty_output(controller) - controller.each_char { |c| print(c) } - rescue Errno::EIO - # Linux marks a PTY as an I/O error when its peer closes, so treat this as EOF: - # https://github.com/torvalds/linux/blob/master/drivers/tty/pty.c - end - sig { void } def record_sandbox_log; end diff --git a/Library/Homebrew/tap.rb b/Library/Homebrew/tap.rb index baf8fc0145..14f3faa6a8 100644 --- a/Library/Homebrew/tap.rb +++ b/Library/Homebrew/tap.rb @@ -613,7 +613,6 @@ class Tap SystemCommand.run!("git", args:, chdir:, env: { "GIT_TERMINAL_PROMPT" => "0" }, print_stderr: true) end - private :git_command! # Install this {Tap}. # diff --git a/Library/Homebrew/test/api/cask_struct_spec.rb b/Library/Homebrew/test/api/cask_struct_spec.rb index 046722bac6..2edd204b9f 100644 --- a/Library/Homebrew/test/api/cask_struct_spec.rb +++ b/Library/Homebrew/test/api/cask_struct_spec.rb @@ -58,8 +58,8 @@ RSpec.describe Homebrew::API::CaskStruct do ) Homebrew::API::CaskStruct::PREDICATES.each do |predicate| - expect(struct.send(:"#{predicate}?")).to be(false), - "expected #{predicate}? to default to false" + expect(struct.public_send(:"#{predicate}?")).to be(false), + "expected #{predicate}? to default to false" end end @@ -76,8 +76,8 @@ RSpec.describe Homebrew::API::CaskStruct do ) Homebrew::API::CaskStruct::PREDICATES.each do |predicate| - expect(struct.send(:"#{predicate}?")).to be(true), - "expected #{predicate}? to be true" + expect(struct.public_send(:"#{predicate}?")).to be(true), + "expected #{predicate}? to be true" end end end @@ -232,7 +232,7 @@ RSpec.describe Homebrew::API::CaskStruct do struct = described_class.deserialize(hash) Homebrew::API::CaskStruct::PREDICATES.each do |predicate| - expect(struct.send(:"#{predicate}?")).to be false + expect(struct.public_send(:"#{predicate}?")).to be false end end @@ -255,7 +255,7 @@ RSpec.describe Homebrew::API::CaskStruct do struct = described_class.deserialize(hash) Homebrew::API::CaskStruct::PREDICATES.each do |predicate| - expect(struct.send(:"#{predicate}?")).to be true + expect(struct.public_send(:"#{predicate}?")).to be true end end end diff --git a/Library/Homebrew/test/api/formula_struct_spec.rb b/Library/Homebrew/test/api/formula_struct_spec.rb index 054039af93..490f1c72e5 100644 --- a/Library/Homebrew/test/api/formula_struct_spec.rb +++ b/Library/Homebrew/test/api/formula_struct_spec.rb @@ -106,8 +106,8 @@ RSpec.describe Homebrew::API::FormulaStruct do ) Homebrew::API::FormulaStruct::PREDICATES.each do |predicate| - expect(struct.send(:"#{predicate}?")).to be(false), - "expected #{predicate}? to default to false" + expect(struct.public_send(:"#{predicate}?")).to be(false), + "expected #{predicate}? to default to false" end end @@ -126,8 +126,8 @@ RSpec.describe Homebrew::API::FormulaStruct do ) Homebrew::API::FormulaStruct::PREDICATES.each do |predicate| - expect(struct.send(:"#{predicate}?")).to be(true), - "expected #{predicate}? to be true" + expect(struct.public_send(:"#{predicate}?")).to be(true), + "expected #{predicate}? to be true" end end end diff --git a/Library/Homebrew/test/bump_spec.rb b/Library/Homebrew/test/bump_spec.rb index fbf983bae6..109c10a66c 100644 --- a/Library/Homebrew/test/bump_spec.rb +++ b/Library/Homebrew/test/bump_spec.rb @@ -7,14 +7,15 @@ RSpec.describe Homebrew::Bump do describe "::redacted_url" do it "masks env-token credentials embedded in a push URL" do allow(GitHub::API).to receive(:credentials).and_return("ghp_secrettoken") - expect(described_class.send(:redacted_url, - "https://x-access-token:ghp_secrettoken@github.com/Homebrew/homebrew-core")) + expect(described_class.redacted_url( + "https://x-access-token:ghp_secrettoken@github.com/Homebrew/homebrew-core", + )) .to eq("https://x-access-token:******@github.com/Homebrew/homebrew-core") end it "leaves a credential-free URL unchanged" do allow(GitHub::API).to receive(:credentials).and_return(nil) - expect(described_class.send(:redacted_url, "https://github.com/Homebrew/homebrew-core")) + expect(described_class.redacted_url("https://github.com/Homebrew/homebrew-core")) .to eq("https://github.com/Homebrew/homebrew-core") end end diff --git a/Library/Homebrew/test/bundle/dsl_spec.rb b/Library/Homebrew/test/bundle/dsl_spec.rb index f3b299d459..bb229e95ba 100644 --- a/Library/Homebrew/test/bundle/dsl_spec.rb +++ b/Library/Homebrew/test/bundle/dsl_spec.rb @@ -189,20 +189,19 @@ RSpec.describe Homebrew::Bundle::Dsl do end it ".sanitize_brew_name" do - expect(described_class.send(:sanitize_brew_name, "homebrew/homebrew/foo")).to eql("foo") - expect(described_class.send(:sanitize_brew_name, "homebrew/homebrew-bar/foo")).to eql("homebrew/bar/foo") - expect(described_class.send(:sanitize_brew_name, "homebrew/bar/foo")).to eql("homebrew/bar/foo") - expect(described_class.send(:sanitize_brew_name, "foo")).to eql("foo") + expect(described_class.sanitize_brew_name("homebrew/homebrew/foo")).to eql("foo") + expect(described_class.sanitize_brew_name("homebrew/homebrew-bar/foo")).to eql("homebrew/bar/foo") + expect(described_class.sanitize_brew_name("homebrew/bar/foo")).to eql("homebrew/bar/foo") + expect(described_class.sanitize_brew_name("foo")).to eql("foo") end it ".sanitize_tap_name" do - expect(described_class.send(:sanitize_tap_name, "homebrew/homebrew-foo")).to eql("homebrew/foo") - expect(described_class.send(:sanitize_tap_name, "homebrew/foo")).to eql("homebrew/foo") + expect(described_class.sanitize_tap_name("homebrew/homebrew-foo")).to eql("homebrew/foo") + expect(described_class.sanitize_tap_name("homebrew/foo")).to eql("homebrew/foo") end it ".sanitize_cask_name" do - expect(described_class.send(:sanitize_cask_name, - "homebrew/cask-versions/adoptopenjdk8")).to eql("adoptopenjdk8") - expect(described_class.send(:sanitize_cask_name, "adoptopenjdk8")).to eql("adoptopenjdk8") + expect(described_class.sanitize_cask_name("homebrew/cask-versions/adoptopenjdk8")).to eql("adoptopenjdk8") + expect(described_class.sanitize_cask_name("adoptopenjdk8")).to eql("adoptopenjdk8") end end diff --git a/Library/Homebrew/test/bundle/installer_spec.rb b/Library/Homebrew/test/bundle/installer_spec.rb index df92791301..9d01f80b0b 100644 --- a/Library/Homebrew/test/bundle/installer_spec.rb +++ b/Library/Homebrew/test/bundle/installer_spec.rb @@ -280,7 +280,7 @@ RSpec.describe Homebrew::Bundle::Installer do output = IO.pipe do |reader, writer| allow(writer).to receive(:tty?).and_return(true) - parallel_installer.send(:write_output, "Installing alpha", stream: writer) + parallel_installer.write_output("Installing alpha", stream: writer) writer.close reader.read @@ -293,7 +293,7 @@ RSpec.describe Homebrew::Bundle::Installer do output = IO.pipe do |reader, writer| allow(writer).to receive(:tty?).and_return(false) - parallel_installer.send(:write_output, "Installing alpha", stream: writer) + parallel_installer.write_output("Installing alpha", stream: writer) writer.close reader.read @@ -417,7 +417,7 @@ RSpec.describe Homebrew::Bundle::Installer do dependency_map = Homebrew::Bundle::ParallelInstaller.new( entries, jobs: 2, no_upgrade: false, verbose: false, force: false, quiet: true, - ).send(:build_dependency_map, entries) + ).build_dependency_map(entries) expect(dependency_map.fetch("beta")).to eq(Set["alpha"]) end @@ -440,7 +440,7 @@ RSpec.describe Homebrew::Bundle::Installer do dependency_map = Homebrew::Bundle::ParallelInstaller.new( entries, jobs: 2, no_upgrade: false, verbose: false, force: false, quiet: true, - ).send(:build_dependency_map, entries) + ).build_dependency_map(entries) expect(dependency_map.fetch("alpha")).to eq(Set["gh"]) end diff --git a/Library/Homebrew/test/bundle/skipper_spec.rb b/Library/Homebrew/test/bundle/skipper_spec.rb index e555de0dee..b9f032974d 100644 --- a/Library/Homebrew/test/bundle/skipper_spec.rb +++ b/Library/Homebrew/test/bundle/skipper_spec.rb @@ -13,8 +13,8 @@ RSpec.describe Homebrew::Bundle::Skipper do allow(ENV).to receive(:[]).with("HOMEBREW_BUNDLE_BREW_SKIP").and_return("mysql") allow(ENV).to receive(:[]).with("HOMEBREW_BUNDLE_TAP_SKIP").and_return("org/repo") allow(Formatter).to receive(:warning) - skipper.instance_variable_set(:@skipped_entries, nil) - skipper.instance_variable_set(:@failed_taps, nil) + skipper.skipped_entries = nil + skipper.failed_taps = nil end describe ".skip?" do diff --git a/Library/Homebrew/test/cache_store_spec.rb b/Library/Homebrew/test/cache_store_spec.rb index da42646167..b86340363d 100644 --- a/Library/Homebrew/test/cache_store_spec.rb +++ b/Library/Homebrew/test/cache_store_spec.rb @@ -99,7 +99,7 @@ RSpec.describe CacheStoreDatabase do context "without an open database" do before do - sample_db.instance_variable_set(:@db, nil) + sample_db.db = nil end it "does not raise an error when `close` is called on the database" do diff --git a/Library/Homebrew/test/cask/artifact/abstract_uninstall_spec.rb b/Library/Homebrew/test/cask/artifact/abstract_uninstall_spec.rb index 79e2203f69..c0989be59f 100644 --- a/Library/Homebrew/test/cask/artifact/abstract_uninstall_spec.rb +++ b/Library/Homebrew/test/cask/artifact/abstract_uninstall_spec.rb @@ -21,7 +21,7 @@ RSpec.describe Cask::Artifact::AbstractUninstall, :cask do it "skips relative paths" do expect do - expect(artifact.send(:each_resolved_path, :delete, ["relative/path"]).to_a).to be_empty + expect(artifact.each_resolved_path(:delete, ["relative/path"]).to_a).to be_empty end.to output(%r{Skipping delete for relative path 'relative/path'\.}).to_stderr end @@ -36,7 +36,7 @@ RSpec.describe Cask::Artifact::AbstractUninstall, :cask do tmpdir/"nested/./#{valid_path.basename}", ].each do |invalid_path| expect do - expect(artifact.send(:each_resolved_path, :delete, [invalid_path.to_s]).to_a).to be_empty + expect(artifact.each_resolved_path(:delete, [invalid_path.to_s]).to_a).to be_empty end.to output( /Skipping delete for path with relative segments '#{Regexp.escape(invalid_path.to_s)}'\./, ).to_stderr @@ -49,7 +49,7 @@ RSpec.describe Cask::Artifact::AbstractUninstall, :cask do invalid_path = "~/../each_resolved_path_#{artifact_dsl_key}" expect do - expect(artifact.send(:each_resolved_path, :delete, [invalid_path]).to_a).to be_empty + expect(artifact.each_resolved_path(:delete, [invalid_path]).to_a).to be_empty end.to output( /Skipping delete for path with relative segments '#{Regexp.escape(invalid_path)}'\./, ).to_stderr @@ -66,7 +66,7 @@ RSpec.describe Cask::Artifact::AbstractUninstall, :cask do allow(artifact).to receive(:undeletable?) { |target| target == undeletable_path } expect do - expect(artifact.send(:each_resolved_path, :delete, ["#{glob_dir}/*.plist"]).to_a) + expect(artifact.each_resolved_path(:delete, ["#{glob_dir}/*.plist"]).to_a) .to eq([["#{glob_dir}/*.plist", [safe_path]]]) end.to output( /Skipping delete for undeletable path '#{Regexp.escape(undeletable_path.to_s)}'\./, @@ -81,7 +81,7 @@ RSpec.describe Cask::Artifact::AbstractUninstall, :cask do allow(MacOS).to receive(:version).and_return(MacOSVersion.from_symbol(:ventura)) expect do - artifact.send(:each_resolved_path, :delete, ["/tmp/each_resolved_path_#{artifact_dsl_key}"]).to_a + artifact.each_resolved_path(:delete, ["/tmp/each_resolved_path_#{artifact_dsl_key}"]).to_a end.to raise_error(SystemExit) .and output(/Full Disk Access/).to_stderr end diff --git a/Library/Homebrew/test/cask/artifact/app_spec.rb b/Library/Homebrew/test/cask/artifact/app_spec.rb index 656c078ed1..0ae3b71a2d 100644 --- a/Library/Homebrew/test/cask/artifact/app_spec.rb +++ b/Library/Homebrew/test/cask/artifact/app_spec.rb @@ -315,7 +315,7 @@ RSpec.describe Cask::Artifact::App, :cask do allow(MacOS).to receive(:version).and_return(MacOSVersion.from_symbol(:sonoma)) - expect(app.send(:backup_copy_args, target_path, source_path)).to eq(["-c", "-pR", target_path, source_path]) + expect(app.backup_copy_args(target_path, source_path)).to eq(["-c", "-pR", target_path, source_path]) end it "uses portable copy arguments on older macOS versions", :needs_macos do @@ -323,7 +323,7 @@ RSpec.describe Cask::Artifact::App, :cask do allow(MacOS).to receive(:version).and_return(MacOSVersion.from_symbol(:ventura)) - expect(app.send(:backup_copy_args, target_path, source_path)).to eq(["-pR", target_path, source_path]) + expect(app.backup_copy_args(target_path, source_path)).to eq(["-pR", target_path, source_path]) end it "uses portable copy arguments across filesystems", :needs_macos do @@ -335,7 +335,7 @@ RSpec.describe Cask::Artifact::App, :cask do allow(source_path).to receive(:dirname).and_return(source_dir) allow(source_dir).to receive(:stat).and_return(instance_double(File::Stat, dev: 2)) - expect(app.send(:backup_copy_args, target_path, source_path)).to eq(["-pR", target_path, source_path]) + expect(app.backup_copy_args(target_path, source_path)).to eq(["-pR", target_path, source_path]) end end diff --git a/Library/Homebrew/test/cask/artifact/generated_completion_spec.rb b/Library/Homebrew/test/cask/artifact/generated_completion_spec.rb index dc52d46bbb..4106dc0c4a 100644 --- a/Library/Homebrew/test/cask/artifact/generated_completion_spec.rb +++ b/Library/Homebrew/test/cask/artifact/generated_completion_spec.rb @@ -5,13 +5,11 @@ RSpec.describe Cask::Artifact::GeneratedCompletion, :cask do let(:staged_path) { Pathname(Dir.mktmpdir) } let(:cask) do - tmp_staged = staged_path Cask::Cask.new("test-generated-completion") do version "1.0" sha256 :no_check url "file:///dev/null" generate_completions_from_executable "bin/foo", "completions" - instance_variable_set(:@staged_path, tmp_staged) end end @@ -20,6 +18,7 @@ RSpec.describe Cask::Artifact::GeneratedCompletion, :cask do let(:fish_dir) { cask.config.fish_completion } before do + allow(cask).to receive(:staged_path).and_return(staged_path) (staged_path/"bin").mkpath (staged_path/"bin/foo").write("#!/bin/sh\necho \"$SHELL completion\"") (staged_path/"bin/foo").chmod(0755) @@ -134,14 +133,12 @@ RSpec.describe Cask::Artifact::GeneratedCompletion, :cask do context "with specific shells and format" do let(:cask) do - tmp_staged = staged_path Cask::Cask.new("test-generated-completion") do version "1.0" sha256 :no_check url "file:///dev/null" generate_completions_from_executable "bin/foo", "completions", shells: [:zsh], shell_parameter_format: :arg, base_name: "bar" - instance_variable_set(:@staged_path, tmp_staged) end end @@ -175,14 +172,12 @@ RSpec.describe Cask::Artifact::GeneratedCompletion, :cask do context "with string shells" do let(:cask) do - tmp_staged = staged_path Cask::Cask.new("test-generated-completion") do version "1.0" sha256 :no_check url "file:///dev/null" generate_completions_from_executable "bin/foo", "completions", shells: %w[bash zsh fish pwsh] - instance_variable_set(:@staged_path, tmp_staged) end end diff --git a/Library/Homebrew/test/cask/artifact/relocated_spec.rb b/Library/Homebrew/test/cask/artifact/relocated_spec.rb index 361c66f649..02ddd157bd 100644 --- a/Library/Homebrew/test/cask/artifact/relocated_spec.rb +++ b/Library/Homebrew/test/cask/artifact/relocated_spec.rb @@ -29,7 +29,7 @@ RSpec.describe Cask::Artifact::Relocated, :cask do expect(command).not_to receive(:run) expect(command).not_to receive(:run!) - artifact.send(:add_altname_metadata, file, altname, command: command) + artifact.add_altname_metadata(file, altname, command: command) end end @@ -46,7 +46,7 @@ RSpec.describe Cask::Artifact::Relocated, :cask do print_stderr: false) expect(command).to receive(:run!).twice - artifact.send(:add_altname_metadata, file, altname, command: command) + artifact.add_altname_metadata(file, altname, command: command) end end end diff --git a/Library/Homebrew/test/cask/artifact/shared_examples/uninstall_zap.rb b/Library/Homebrew/test/cask/artifact/shared_examples/uninstall_zap.rb index 063d7e4276..7d0fd5df79 100644 --- a/Library/Homebrew/test/cask/artifact/shared_examples/uninstall_zap.rb +++ b/Library/Homebrew/test/cask/artifact/shared_examples/uninstall_zap.rb @@ -186,9 +186,8 @@ RSpec.shared_examples "#uninstall_phase or #zap_phase" do .with("/bin/launchctl", args: ["list"]) .and_return(instance_double(SystemCommand::Result, stdout: launchctl_list)) - expect(subject.send(:find_launchctl_with_wildcard, - "my.fancy.package.service.*")).to eq(["my.fancy.package.service.12345", - "my.fancy.package.service.test"]) + expect(subject.find_launchctl_with_wildcard("my.fancy.package.service.*")) + .to eq(["my.fancy.package.service.12345", "my.fancy.package.service.test"]) end end diff --git a/Library/Homebrew/test/cask/artifact/uninstall_spec.rb b/Library/Homebrew/test/cask/artifact/uninstall_spec.rb index c23e3aa3ea..cfc4ffba8a 100644 --- a/Library/Homebrew/test/cask/artifact/uninstall_spec.rb +++ b/Library/Homebrew/test/cask/artifact/uninstall_spec.rb @@ -149,7 +149,7 @@ RSpec.describe Cask::Artifact::Uninstall, :cask do allow(artifact).to receive(:quit).with(bundle_id) .and_return(instance_double(SystemCommand::Result, success?: true)) - artifact.send(:uninstall_quit, bundle_id, upgrade: true, command: fake_system_command) + artifact.uninstall_quit(bundle_id, upgrade: true, command: fake_system_command) expect(artifact.bundle_ids_to_reopen).to eq [bundle_id] end @@ -159,7 +159,7 @@ RSpec.describe Cask::Artifact::Uninstall, :cask do allow(artifact).to receive(:quit).with(bundle_id) .and_return(instance_double(SystemCommand::Result, success?: true)) - artifact.send(:uninstall_quit, bundle_id, upgrade: false, command: fake_system_command) + artifact.uninstall_quit(bundle_id, upgrade: false, command: fake_system_command) expect(artifact.bundle_ids_to_reopen).to be_empty end @@ -171,7 +171,7 @@ RSpec.describe Cask::Artifact::Uninstall, :cask do allow(Timeout).to receive(:timeout).and_raise(Timeout::Error) expect do - artifact.send(:uninstall_quit, bundle_id, upgrade: true, command: fake_system_command) + artifact.uninstall_quit(bundle_id, upgrade: true, command: fake_system_command) end.to output(/did not quit/).to_stderr expect(artifact.bundle_ids_to_reopen).to be_empty diff --git a/Library/Homebrew/test/cask/audit_spec.rb b/Library/Homebrew/test/cask/audit_spec.rb index c6a0c9d783..6b516b70ce 100644 --- a/Library/Homebrew/test/cask/audit_spec.rb +++ b/Library/Homebrew/test/cask/audit_spec.rb @@ -542,7 +542,7 @@ RSpec.describe Cask::Audit, :cask do allow(Cask::Quarantine).to receive(:available?).and_return(false) expect(Cask::Quarantine).not_to receive(:detect) - audit.send(:extract_artifacts) + audit.extract_artifacts end end @@ -555,9 +555,9 @@ RSpec.describe Cask::Audit, :cask do let(:cask_token) { "basic-cask" } it "returns existing `@livecheck_result` value" do - audit.instance_variable_set(:@livecheck_result, :auto_detected) + audit.livecheck_result = :auto_detected expect(run).not_to error_with(message) - audit.instance_variable_set(:@livecheck_result, nil) + audit.livecheck_result = nil end end @@ -1237,7 +1237,7 @@ RSpec.describe Cask::Audit, :cask do end it "normalizes 10.16.0 minimum macOS to Big Sur" do - expect(audit.send(:normalize_min_os, "10.16.0")).to eq(MacOSVersion.from_symbol(:big_sur)) + expect(audit.normalize_min_os("10.16.0")).to eq(MacOSVersion.from_symbol(:big_sur)) end end diff --git a/Library/Homebrew/test/cask/auditor_spec.rb b/Library/Homebrew/test/cask/auditor_spec.rb index a813391cf3..a9e648b5d0 100644 --- a/Library/Homebrew/test/cask/auditor_spec.rb +++ b/Library/Homebrew/test/cask/auditor_spec.rb @@ -39,35 +39,31 @@ RSpec.describe Cask::Auditor, :cask do it "returns true if @any_named_args is true" do auditor_obj = auditor.new(cask, any_named_args: true) - expect(auditor_obj.send(:output_summary?)).to be(true) + expect(auditor_obj.output_summary?).to be(true) end it "returns true if @audit_strict is true" do auditor_obj = auditor.new(cask, audit_strict: true) - expect(auditor_obj.send(:output_summary?)).to be(true) + expect(auditor_obj.output_summary?).to be(true) end it "returns false if the audit argument is nil" do auditor_obj = auditor.new(cask) - expect(auditor_obj.send(:output_summary?)).to be(false) - expect(auditor_obj.send(:output_summary?, nil)).to be(false) + expect(auditor_obj.output_summary?).to be(false) + expect(auditor_obj.output_summary?(nil)).to be(false) end it "returns false if there are no audit errors" do auditor_obj = auditor.new(cask) audit = Cask::Audit.new(cask) - expect(auditor_obj.send(:output_summary?, audit)).to be(false) + expect(auditor_obj.output_summary?(audit)).to be(false) end it "returns true if there are audit errors" do auditor_obj = auditor.new(cask) audit = Cask::Audit.new(cask) - audit.instance_variable_set(:@errors, [{ - message: nil, - location: nil, - corrected: false, - }]) - expect(auditor_obj.send(:output_summary?, audit)).to be(true) + audit.add_error(nil) + expect(auditor_obj.output_summary?(audit)).to be(true) end end end diff --git a/Library/Homebrew/test/cask/cask_loader_spec.rb b/Library/Homebrew/test/cask/cask_loader_spec.rb index cf2a0b6301..95f19e1855 100644 --- a/Library/Homebrew/test/cask/cask_loader_spec.rb +++ b/Library/Homebrew/test/cask/cask_loader_spec.rb @@ -425,7 +425,7 @@ RSpec.describe Cask::CaskLoader, :cask do it "raises CaskUnreadableError when loaded from installed caskfile" do loader = Cask::CaskLoader::FromPathLoader.new(cask_file) - loader.instance_variable_set(:@from_installed_caskfile, true) + loader.from_installed_caskfile = true expect { loader.load(config: nil) }.to raise_error(Cask::CaskUnreadableError, /appcast/) end end @@ -491,7 +491,7 @@ RSpec.describe Cask::CaskLoader, :cask do it "raises CaskUnreadableError when loaded from installed caskfile" do loader = Cask::CaskLoader::FromPathLoader.new(cask_file) - loader.instance_variable_set(:@from_installed_caskfile, true) + loader.from_installed_caskfile = true expect { loader.load(config: nil) }.to raise_error(Cask::CaskUnreadableError, /Unknown key: :formula/) end end diff --git a/Library/Homebrew/test/cask/caskroom_spec.rb b/Library/Homebrew/test/cask/caskroom_spec.rb index 233a6ce62a..e0e2a217f8 100644 --- a/Library/Homebrew/test/cask/caskroom_spec.rb +++ b/Library/Homebrew/test/cask/caskroom_spec.rb @@ -4,7 +4,7 @@ require "cask/caskroom" RSpec.describe Cask::Caskroom do - before { described_class.instance_variable_set(:@expected_caskroom_group, nil) } + before { described_class.expected_caskroom_group = nil } describe ".ensure_caskroom_exists" do it "changes the group when sudo is unnecessary and the group is wrong" do diff --git a/Library/Homebrew/test/cask/download_spec.rb b/Library/Homebrew/test/cask/download_spec.rb index 205ff31f8a..47bc1eb61a 100644 --- a/Library/Homebrew/test/cask/download_spec.rb +++ b/Library/Homebrew/test/cask/download_spec.rb @@ -3,7 +3,7 @@ RSpec.describe Cask::Download, :cask do describe "#download_name" do - subject(:download_name) { described_class.new(cask).send(:download_name) } + subject(:download_name) { described_class.new(cask).download_name } let(:token) { "example-cask" } let(:full_token) { token } diff --git a/Library/Homebrew/test/cask/dsl/version_spec.rb b/Library/Homebrew/test/cask/dsl/version_spec.rb index d66fdf54f0..9fa357513c 100644 --- a/Library/Homebrew/test/cask/dsl/version_spec.rb +++ b/Library/Homebrew/test/cask/dsl/version_spec.rb @@ -73,7 +73,7 @@ RSpec.describe Cask::DSL::Version, :cask do end shared_examples "version expectations hash" do |method, hash| - subject { version.send(method) } + subject { version.public_send(method) } include_examples "expectations hash", :raw_version, { :latest => "latest", diff --git a/Library/Homebrew/test/cask/dsl_spec.rb b/Library/Homebrew/test/cask/dsl_spec.rb index e84198b513..e75ab116ab 100644 --- a/Library/Homebrew/test/cask/dsl_spec.rb +++ b/Library/Homebrew/test/cask/dsl_spec.rb @@ -736,7 +736,7 @@ RSpec.describe Cask::DSL, :cask, :no_api do it "allows installer manual to be specified" do installer = cask.artifacts.first - expect(installer.instance_variable_get(:@manual_install)).to be true + expect(installer.manual_install).to be true expect(installer.path).to eq(Pathname("Caffeine.app")) end end diff --git a/Library/Homebrew/test/cask/installer_spec.rb b/Library/Homebrew/test/cask/installer_spec.rb index 044075a75c..1b59c1674b 100644 --- a/Library/Homebrew/test/cask/installer_spec.rb +++ b/Library/Homebrew/test/cask/installer_spec.rb @@ -921,7 +921,7 @@ RSpec.describe Cask::Installer, :cask do allow(cask).to receive(:staged_path).and_return(staged_path) installer = described_class.new(cask) - installer.send(:process_rename_operations) + installer.process_rename_operations expect(staged_path / "Renamed App.app").to be_a_directory expect(staged_path / "Original App.app").not_to exist @@ -941,7 +941,7 @@ RSpec.describe Cask::Installer, :cask do allow(cask).to receive(:staged_path).and_return(staged_path) installer = described_class.new(cask) - installer.send(:process_rename_operations) + installer.process_rename_operations expect(staged_path / "Final Name.app").to be_a_directory expect(staged_path / "Original.app").not_to exist @@ -961,7 +961,7 @@ RSpec.describe Cask::Installer, :cask do allow(cask).to receive(:staged_path).and_return(staged_path) installer = described_class.new(cask) - installer.send(:process_rename_operations) + installer.process_rename_operations expect(staged_path / "Test App.pkg").to be_a_file expect((staged_path / "Test App.pkg").read).to eq("test content") @@ -982,7 +982,7 @@ RSpec.describe Cask::Installer, :cask do installer = described_class.new(cask) - expect { installer.send(:process_rename_operations) }.not_to raise_error + expect { installer.process_rename_operations }.not_to raise_error expect(staged_path / "Different.app").to be_a_directory expect(staged_path / "Target.app").not_to exist end diff --git a/Library/Homebrew/test/cmd/bundle/check_subcommand_spec.rb b/Library/Homebrew/test/cmd/bundle/check_subcommand_spec.rb index beeb01aceb..a34115bf89 100644 --- a/Library/Homebrew/test/cmd/bundle/check_subcommand_spec.rb +++ b/Library/Homebrew/test/cmd/bundle/check_subcommand_spec.rb @@ -102,7 +102,7 @@ RSpec.describe Homebrew::Cmd::Bundle::CheckSubcommand, :no_api do end it "raises an error for an implicitly unlinked non-keg-only formula" do - Homebrew::Bundle::Brew.instance_variable_set(:@formulae_by_name, { "abc" => { link?: false } }) + Homebrew::Bundle::Brew.formulae_by_name = { "abc" => { link?: false } } allow_any_instance_of(Pathname).to receive(:read).and_return("brew 'abc'") allow(Formula["abc"]).to receive(:linked?).and_return(false) @@ -111,7 +111,7 @@ RSpec.describe Homebrew::Cmd::Bundle::CheckSubcommand, :no_api do end it "does not raise an error when live link status satisfies an implicit check" do - Homebrew::Bundle::Brew.instance_variable_set(:@formulae_by_name, { "abc" => { link?: false } }) + Homebrew::Bundle::Brew.formulae_by_name = { "abc" => { link?: false } } allow_any_instance_of(Pathname).to receive(:read).and_return("brew 'abc'") allow(Formula["abc"]).to receive(:linked?).and_return(true) @@ -130,7 +130,7 @@ RSpec.describe Homebrew::Cmd::Bundle::CheckSubcommand, :no_api do end it "outputs the implicit link status error" do - Homebrew::Bundle::Brew.instance_variable_set(:@formulae_by_name, { "abc" => { link?: true } }) + Homebrew::Bundle::Brew.formulae_by_name = { "abc" => { link?: true } } allow_any_instance_of(Pathname).to receive(:read).and_return("brew 'abc'") allow(Formula["abc"]).to receive(:linked?).and_return(true) diff --git a/Library/Homebrew/test/cmd/fetch_spec.rb b/Library/Homebrew/test/cmd/fetch_spec.rb index 64e24fb1d2..9c6fe6d878 100644 --- a/Library/Homebrew/test/cmd/fetch_spec.rb +++ b/Library/Homebrew/test/cmd/fetch_spec.rb @@ -89,7 +89,7 @@ RSpec.describe Homebrew::Cmd::FetchCmd do describe "#cask_downloads", :cask do it "collects one download per distinct URL across all platforms" do cmd = described_class.new(["--cask", "--all-platforms", "sha256-os"]) - basenames = cmd.send(:cask_downloads, Cask::CaskLoader.load("sha256-os")) + basenames = cmd.cask_downloads(Cask::CaskLoader.load("sha256-os")) .map { |download| File.basename(download.url.to_s) } expect(basenames).to contain_exactly("caffeine-arm-darwin.zip", "caffeine-intel-darwin.zip", "caffeine-arm-linux.zip", "caffeine-intel-linux.zip") @@ -97,12 +97,12 @@ RSpec.describe Homebrew::Cmd::FetchCmd do it "skips arches the cask's depends_on arch excludes" do cmd = described_class.new(["--cask", "--os=macos", "--arch=intel", "depends-on-arch-arm64"]) - expect(cmd.send(:cask_downloads, Cask::CaskLoader.load("depends-on-arch-arm64"))).to be_empty + expect(cmd.cask_downloads(Cask::CaskLoader.load("depends-on-arch-arm64"))).to be_empty end it "collapses to a single download for a cask without on_system blocks" do cmd = described_class.new(["--cask", "--all-platforms", "local-caffeine"]) - expect(cmd.send(:cask_downloads, Cask::CaskLoader.load("local-caffeine")).length).to eq(1) + expect(cmd.cask_downloads(Cask::CaskLoader.load("local-caffeine")).length).to eq(1) end end end diff --git a/Library/Homebrew/test/cmd/info_spec.rb b/Library/Homebrew/test/cmd/info_spec.rb index 6c4b9bcd01..f22c87c5af 100644 --- a/Library/Homebrew/test/cmd/info_spec.rb +++ b/Library/Homebrew/test/cmd/info_spec.rb @@ -256,7 +256,7 @@ RSpec.describe Homebrew::Cmd::Info do Formula from https://example.com/testball.rb Not installed EOS - expect { info.send(:info_formula_summary, formula) } + expect { info.info_formula_summary(formula) } .to output(expected_output).to_stdout .and not_to_output.to_stderr end @@ -290,7 +290,7 @@ RSpec.describe Homebrew::Cmd::Info do allow(info).to receive(:github_info).with(formula).and_return("https://example.com/testball.rb") allow(formula).to receive_messages(core_formula?: false, missing_library_linkage: [[], Set.new]) - expect { info.send(:info_formula, formula) } + expect { info.info_formula(formula) } .to output(/Installs from source: yes/).to_stdout .and not_to_output(/Metadata/).to_stdout .and not_to_output(/supports macOS and Linux/).to_stdout @@ -308,7 +308,7 @@ RSpec.describe Homebrew::Cmd::Info do allow(other).to receive(:full_name).and_return("someuser/tap/other") allow(Formulary).to receive(:factory).with("other").and_return(other) - expect { info.send(:info_formula, formula) } + expect { info.info_formula(formula) } .to output(%r{Conflicts with:\n someuser/tap/other}).to_stdout end @@ -321,7 +321,7 @@ RSpec.describe Homebrew::Cmd::Info do allow(info).to receive(:github_info).with(formula).and_return("https://example.com/testball.rb") allow(Formulary).to receive(:factory).with("testball").and_return(formula) - expect { info.send(:info_formula, formula) } + expect { info.info_formula(formula) } .not_to output(/Conflicts with:/).to_stdout end @@ -338,7 +338,7 @@ RSpec.describe Homebrew::Cmd::Info do allow(info).to receive(:github_info).with(formula).and_return("https://example.com/testball.rb") allow(formula).to receive_messages(core_formula?: false, missing_library_linkage: [[], Set.new]) - expect { info.send(:info_formula, formula) } + expect { info.info_formula(formula) } .to output(/==> .*testball.*\(deprecated\):/).to_stdout .and not_to_output.to_stderr end @@ -356,7 +356,7 @@ RSpec.describe Homebrew::Cmd::Info do allow(info).to receive(:github_info).with(formula).and_return("https://example.com/testball.rb") allow(formula).to receive_messages(core_formula?: false, missing_library_linkage: [[], Set.new]) - expect { info.send(:info_formula, formula) } + expect { info.info_formula(formula) } .to output(/==> .*testball.*\(disabled\):/).to_stdout .and not_to_output.to_stderr end @@ -379,7 +379,7 @@ RSpec.describe Homebrew::Cmd::Info do to_formulae_and_casks_and_unavailable: [core, core], ) - expect { info.send(:print_info) } + expect { info.print_info } .to output(%r{ataraxy-labs/tap/testball.*homebrew/core/testball.*Not installed}m).to_stdout end @@ -391,7 +391,7 @@ RSpec.describe Homebrew::Cmd::Info do to_formulae_and_casks_and_unavailable: [error], ) - expect { info.send(:print_info) } + expect { info.print_info } .to output(/No available formula or cask with the name "nonexistent-formula"/).to_stderr end @@ -409,7 +409,7 @@ RSpec.describe Homebrew::Cmd::Info do allow(shadowing).to receive_messages(tap: Tap.fetch("ataraxy-labs/tap"), full_name: "ataraxy-labs/tap/testball") allow(Formulary).to receive(:factory).with("ataraxy-labs/tap/testball").and_return(shadowing) - expect { info.send(:info_formula, formula) } + expect { info.info_formula(formula) } .to output(%r{homebrew/core/testball.*Not installed.*ataraxy-labs/tap/testball}m).to_stdout end @@ -431,7 +431,7 @@ RSpec.describe Homebrew::Cmd::Info do end allow(Formulary).to receive(:factory).with("ataraxy-labs/tap/testball").and_return(keg_formula) - expect(info.send(:installed_resolution, formula)).to eq([keg_formula, shadowing_tap]) + expect(info.installed_resolution(formula)).to eq([keg_formula, shadowing_tap]) end it "resolves the keg's own name when it differs from the formula (installed via alias)" do @@ -443,7 +443,7 @@ RSpec.describe Homebrew::Cmd::Info do keg_formula = formula("stripe") { url "https://brew.sh/stripe-1.0.tar.gz" } allow(Formulary).to receive(:factory).with("stripe/stripe-cli/stripe").and_return(keg_formula) - expect(info.send(:installed_resolution, formula)).to eq([keg_formula, Tap.fetch("homebrew/core")]) + expect(info.installed_resolution(formula)).to eq([keg_formula, Tap.fetch("homebrew/core")]) end it "returns the original formula and no shadowing tap when the install receipt has no tap" do @@ -455,7 +455,7 @@ RSpec.describe Homebrew::Cmd::Info do tab.tabfile = keg_path/AbstractTab::FILENAME tab.write - expect(info.send(:installed_resolution, formula)).to eq([formula, nil]) + expect(info.installed_resolution(formula)).to eq([formula, nil]) end it "returns the original formula and no shadowing tap when the install receipt's tap matches" do @@ -469,7 +469,7 @@ RSpec.describe Homebrew::Cmd::Info do tab.write allow(formula).to receive(:tap).and_return(Tap.fetch("homebrew/core")) - expect(info.send(:installed_resolution, formula)).to eq([formula, nil]) + expect(info.installed_resolution(formula)).to eq([formula, nil]) end it "warns about a shadowing tap when info_formula is given one" do @@ -481,7 +481,7 @@ RSpec.describe Homebrew::Cmd::Info do allow(info).to receive(:github_info).with(formula).and_return("https://example.com/testball.rb") allow(formula).to receive_messages(core_formula?: false, missing_library_linkage: [[], Set.new]) - expect { info.send(:info_formula, formula, shadowed_by: Tap.fetch("homebrew/core")) } + expect { info.info_formula(formula, shadowed_by: Tap.fetch("homebrew/core")) } .to output(%r{Warning: `testball` shadows `homebrew/core/testball`}).to_stdout .and not_to_output.to_stderr end @@ -495,7 +495,7 @@ RSpec.describe Homebrew::Cmd::Info do allow(formula).to receive(:tap).and_return(Tap.fetch("homebrew/core")) qualified = Set["homebrew/core/testball"] - expect(info.send(:formula_qualified_by_user?, formula, qualified)).to be(true) + expect(info.formula_qualified_by_user?(formula, qualified)).to be(true) end it "treats a bare unqualified input as not user-qualified" do @@ -505,7 +505,7 @@ RSpec.describe Homebrew::Cmd::Info do url "https://brew.sh/testball-0.1.tar.gz" end - expect(info.send(:formula_qualified_by_user?, formula, Set.new)).to be(false) + expect(info.formula_qualified_by_user?(formula, Set.new)).to be(false) end it "--json swaps an unqualified-input formula to its installed tap" do @@ -606,7 +606,7 @@ RSpec.describe Homebrew::Cmd::Info do "==> Dependencies\nRequired \\(1\\): .*bar.*\n" \ "Recursive Runtime \\(2\\): 1 installed .*✔, 1 missing .*✘\nDependents: 1", ) - expect { info.send(:info_formula, formula) } + expect { info.info_formula(formula) } .to output(expected_output).to_stdout .and not_to_output(/^Dependencies: /).to_stdout .and not_to_output.to_stderr @@ -643,7 +643,7 @@ RSpec.describe Homebrew::Cmd::Info do allow(info).to receive(:github_info).with(formula).and_return("https://example.com/testball.rb") allow(formula).to receive_messages(core_formula?: false, missing_library_linkage: [[], Set.new]) - expect { info.send(:info_formula, formula) } + expect { info.info_formula(formula) } .to output(/^Dependents \(2\): another-dependent, some-dependent$/).to_stdout .and not_to_output(/^Dependents: /).to_stdout .and not_to_output.to_stderr @@ -680,7 +680,7 @@ RSpec.describe Homebrew::Cmd::Info do allow(formula).to receive_messages(core_formula?: false, missing_library_linkage: [[], Set.new]) allow(direct_dependency).to receive(:satisfied?).and_return(true) - expect { info.send(:info_formula, formula) } + expect { info.info_formula(formula) } .to output(/Recursive Runtime \(1\): all installed .*✔/).to_stdout .and not_to_output(/missing/).to_stdout .and not_to_output.to_stderr @@ -708,7 +708,7 @@ RSpec.describe Homebrew::Cmd::Info do allow(info).to receive(:github_info).with(formula).and_return("https://example.com/testball.rb") allow(formula).to receive_messages(core_formula?: false, missing_library_linkage: [[], Set.new]) - expect { info.send(:info_formula, formula) } + expect { info.info_formula(formula) } .to output(/Required \(1\): .*bar.*✘/).to_stdout .and not_to_output.to_stderr end @@ -745,7 +745,7 @@ RSpec.describe Homebrew::Cmd::Info do allow(info).to receive(:github_info).with(formula).and_return("https://example.com/testball.rb") allow(formula).to receive_messages(core_formula?: false, missing_library_linkage: [[], Set.new]) - expect { info.send(:info_formula, formula) } + expect { info.info_formula(formula) } .to output(/Required \(1\): .*bar.*✔/).to_stdout .and not_to_output.to_stderr end @@ -782,7 +782,7 @@ RSpec.describe Homebrew::Cmd::Info do allow(info).to receive(:github_info).with(formula).and_return("https://example.com/testball.rb") allow(formula).to receive_messages(core_formula?: false, missing_library_linkage: [[], Set.new]) - expect { info.send(:info_formula, formula) } + expect { info.info_formula(formula) } .to output(/Required \(1\): .*bar.*↑/).to_stdout .and not_to_output.to_stderr end @@ -812,7 +812,7 @@ RSpec.describe Homebrew::Cmd::Info do allow(info).to receive(:github_info).with(formula).and_return("https://example.com/testball.rb") allow(formula).to receive_messages(core_formula?: false, missing_library_linkage: [[], Set.new]) - expect { info.send(:info_formula, formula) } + expect { info.info_formula(formula) } .to output(/Required \(1\): .*bar.*✔/).to_stdout .and not_to_output.to_stderr end @@ -842,7 +842,7 @@ RSpec.describe Homebrew::Cmd::Info do allow(info).to receive(:github_info).with(formula).and_return("https://example.com/testball.rb") allow(formula).to receive_messages(core_formula?: false, missing_library_linkage: [[], Set.new]) - expect { info.send(:info_formula, formula) } + expect { info.info_formula(formula) } .to output(/Required \(1\): .*bar.*↑/).to_stdout .and not_to_output.to_stderr end @@ -872,7 +872,7 @@ RSpec.describe Homebrew::Cmd::Info do allow(info).to receive(:github_info).with(formula).and_return("https://example.com/testball.rb") allow(formula).to receive_messages(core_formula?: false, missing_library_linkage: [[], Set.new]) - expect { info.send(:info_formula, formula) } + expect { info.info_formula(formula) } .to output(/Required \(1\): .*pkg-config.*✔/).to_stdout .and not_to_output.to_stderr end @@ -892,7 +892,7 @@ RSpec.describe Homebrew::Cmd::Info do allow(info).to receive(:github_info).with(formula).and_return("https://example.com/testball.rb") allow(formula).to receive_messages(core_formula?: false, missing_library_linkage: [[], Set.new]) - expect { info.send(:info_formula, formula) } + expect { info.info_formula(formula) } .to output(/Required \(1\): bar\n/).to_stdout .and not_to_output.to_stderr end @@ -919,7 +919,7 @@ RSpec.describe Homebrew::Cmd::Info do allow(info).to receive(:github_info).with(formula).and_return("https://example.com/testball.rb") allow(formula).to receive_messages(core_formula?: false, missing_library_linkage: [[], Set.new]) - expect { info.send(:info_formula, formula) } + expect { info.info_formula(formula) } .to output(/Required \(1\): .*bar.*✘/).to_stdout .and not_to_output.to_stderr end @@ -952,7 +952,7 @@ RSpec.describe Homebrew::Cmd::Info do allow(info).to receive(:github_info).with(formula).and_return("https://example.com/testball.rb") allow(formula).to receive_messages(core_formula?: false, missing_library_linkage: [[], Set.new]) - expect { info.send(:info_formula, formula) } + expect { info.info_formula(formula) } .to output(/Required \(1\): .*bar.*↑/).to_stdout .and not_to_output.to_stderr end @@ -981,7 +981,7 @@ RSpec.describe Homebrew::Cmd::Info do pour_bottle?: true, ) - expect { info.send(:info_formula, formula) } + expect { info.info_formula(formula) } .to not_to_output(/Build \(1\): .*bar.*/).to_stdout .and not_to_output(/==> Dependencies/).to_stdout .and not_to_output.to_stderr @@ -1004,7 +1004,7 @@ RSpec.describe Homebrew::Cmd::Info do tab.tabfile = keg_path/AbstractTab::FILENAME tab.write - expect { info.send(:info_formula, formula) } + expect { info.info_formula(formula) } .to output(/\A==> testball: 0\.0\.1 → stable 0\.1\n/).to_stdout .and not_to_output.to_stderr end @@ -1025,7 +1025,7 @@ RSpec.describe Homebrew::Cmd::Info do requirements: Requirements.new(LinuxRequirement.new), ) - expect { info.send(:info_formula, formula) } + expect { info.info_formula(formula) } .to output(/Requirements\nRequired: .*Linux/).to_stdout .and not_to_output(/supports Linux/).to_stdout .and not_to_output.to_stderr @@ -1048,7 +1048,7 @@ RSpec.describe Homebrew::Cmd::Info do requirements: Requirements.new(os_requirement), ) - expect { info.send(:info_formula, formula) } + expect { info.info_formula(formula) } .to not_to_output(/Installs from source: yes/).to_stdout .and not_to_output.to_stderr end @@ -1077,7 +1077,7 @@ RSpec.describe Homebrew::Cmd::Info do allow(info).to receive(:github_info).with(formula).and_return("https://example.com/testball.rb") allow(formula).to receive_messages(core_formula?: false, missing_library_linkage: [[], Set.new]) - expect { info.send(:info_formula, formula) } + expect { info.info_formula(formula) } .to output(a_string_including("==> Binaries\nanother\ndaemon\ntestball\n")).to_stdout .and not_to_output.to_stderr end @@ -1101,7 +1101,7 @@ RSpec.describe Homebrew::Cmd::Info do allow(formula).to receive_messages(bottle:, core_formula?: false) allow(info).to receive(:github_info).with(formula).and_return("https://example.com/testball.rb") - expect { info.send(:info_formula, formula) } + expect { info.info_formula(formula) } .to output(a_string_including("==> Binaries\nanother\ndaemon\ntestball\n")).to_stdout .and not_to_output.to_stderr end @@ -1127,7 +1127,7 @@ RSpec.describe Homebrew::Cmd::Info do allow(info).to receive(:github_info).with(formula).and_return("https://example.com/testball.rb") allow(formula).to receive_messages(core_formula?: false, missing_library_linkage: [[], Set.new]) - expect { info.send(:info_formula, formula) } + expect { info.info_formula(formula) } .to not_to_output(/==> Binaries/).to_stdout .and not_to_output.to_stderr end @@ -1150,7 +1150,7 @@ RSpec.describe Homebrew::Cmd::Info do allow(info).to receive(:github_info).with(formula).and_return("https://example.com/testball.rb") allow(formula).to receive_messages(core_formula?: false, missing_library_linkage: [[], Set.new]) - expect { info.send(:info_formula, formula) } + expect { info.info_formula(formula) } .to not_to_output(/==> Binaries/).to_stdout .and not_to_output.to_stderr end @@ -1239,7 +1239,7 @@ RSpec.describe Homebrew::Cmd::Info do allow(main_formula).to receive_messages(aliases: ["testball@1.0", "tball", "googleball"], oldnames: []) allow(info).to receive(:github_info).with(main_formula).and_return("https://example.com/testball.rb") - expect { info.send(:info_formula, main_formula) } + expect { info.info_formula(main_formula) } .to output(/^Aliases: testball@1\.0, tball, googleball$/).to_stdout .and not_to_output(/^Old Names:/).to_stdout .and not_to_output.to_stderr @@ -1254,7 +1254,7 @@ RSpec.describe Homebrew::Cmd::Info do allow(main_formula).to receive_messages(aliases: ["testball@1.0", "tball"], oldnames: ["foo", "bar"]) allow(info).to receive(:github_info).with(main_formula).and_return("https://example.com/testball.rb") - expect { info.send(:info_formula, main_formula) } + expect { info.info_formula(main_formula) } .to output(/^Aliases: testball@1\.0, tball\nOld Names: foo, bar$/).to_stdout .and not_to_output.to_stderr end @@ -1268,7 +1268,7 @@ RSpec.describe Homebrew::Cmd::Info do allow(main_formula).to receive_messages(aliases: [], oldnames: ["foo"]) allow(info).to receive(:github_info).with(main_formula).and_return("https://example.com/testball.rb") - expect { info.send(:info_formula, main_formula) } + expect { info.info_formula(main_formula) } .to output(/^Old Names: foo$/).to_stdout .and not_to_output(/^Aliases:/).to_stdout .and not_to_output.to_stderr @@ -1283,7 +1283,7 @@ RSpec.describe Homebrew::Cmd::Info do allow(main_formula).to receive_messages(aliases: [], oldnames: []) allow(info).to receive(:github_info).with(main_formula).and_return("https://example.com/testball.rb") - expect { info.send(:info_formula, main_formula) } + expect { info.info_formula(main_formula) } .to not_to_output(/^Aliases:/).to_stdout .and not_to_output(/^Old Names:/).to_stdout .and not_to_output.to_stderr @@ -1318,7 +1318,7 @@ RSpec.describe Homebrew::Cmd::Info do allow(main_formula).to receive(:versioned_formulae).and_return([versioned]) allow(info).to receive(:github_info).with(main_formula).and_return("https://example.com/testball.rb") - expect { info.send(:info_formula, main_formula) } + expect { info.info_formula(main_formula) } .to output(Regexp.new( "==> Installed Versions\n" \ ".*testball\\b.*\\s+1\\.0\\s+\\(.*\\)\n" \ @@ -1346,7 +1346,7 @@ RSpec.describe Homebrew::Cmd::Info do allow(main_formula).to receive_messages(versioned_formulae: [], outdated?: true) allow(info).to receive(:github_info).with(main_formula).and_return("https://example.com/testball.rb") - expect { info.send(:info_formula, main_formula) } + expect { info.info_formula(main_formula) } .to output(/==> Installed Versions\n.*testball\b.*\s+1\.0 → 2\.0\s+\(/).to_stdout .and not_to_output(/0\.9 →/).to_stdout .and not_to_output.to_stderr @@ -1370,7 +1370,7 @@ RSpec.describe Homebrew::Cmd::Info do allow(main_formula).to receive(:versioned_formulae).and_return([]) allow(info).to receive(:github_info).with(main_formula).and_return("https://example.com/testball.rb") - expect { info.send(:info_formula, main_formula) } + expect { info.info_formula(main_formula) } .to output(/==> Installed Versions\n.*testball\b.*\s+1\.0\s+\(/).to_stdout .and not_to_output(/\s+0\.9\s+\(/).to_stdout .and not_to_output.to_stderr @@ -1406,7 +1406,7 @@ RSpec.describe Homebrew::Cmd::Info do linked_version: PkgVersion.parse("1.0")) allow(info).to receive(:github_info).with(main_formula).and_return("https://example.com/testball.rb") - expect { info.send(:info_formula, main_formula) } + expect { info.info_formula(main_formula) } .to output(/.*testball\b.*\s+1\.0\s+\(.*\)\s+\[Linked\]/).to_stdout .and not_to_output.to_stderr end @@ -1439,7 +1439,7 @@ RSpec.describe Homebrew::Cmd::Info do allow(Formulary).to receive(:factory).with("testball").and_return(parent) allow(info).to receive(:github_info).with(versioned).and_return("https://example.com/testball.rb") - expect { info.send(:info_formula, versioned) } + expect { info.info_formula(versioned) } .to output(Regexp.new( "==> Installed Versions\n" \ ".*testball\\b.*\\s+1\\.0\\s+\\(.*\\)\n" \ @@ -1464,7 +1464,7 @@ RSpec.describe Homebrew::Cmd::Info do allow(main_formula).to receive(:versioned_formulae).and_return([]) allow(info).to receive(:github_info).with(main_formula).and_return("https://example.com/testball.rb") - expect { info.send(:info_formula, main_formula) } + expect { info.info_formula(main_formula) } .to output(/==> Installed Versions\n.*testball\b.*\s+1\.0\s+\(/).to_stdout .and not_to_output.to_stderr end @@ -1491,7 +1491,7 @@ RSpec.describe Homebrew::Cmd::Info do allow(Formulary).to receive(:factory).with("testball").and_return(parent) allow(info).to receive(:github_info).with(versioned).and_return("https://example.com/testball.rb") - expect { info.send(:info_formula, versioned) } + expect { info.info_formula(versioned) } .to output(/==> Installed Versions\n.*testball\b.*\s+1\.0\s+\(/).to_stdout .and not_to_output(/testball@0\.9 \(0\.9\)/).to_stdout .and not_to_output.to_stderr @@ -1515,7 +1515,7 @@ RSpec.describe Homebrew::Cmd::Info do allow(main_formula).to receive(:versioned_formulae).and_return([]) allow(info).to receive(:github_info).with(main_formula).and_return("https://example.com/testball.rb") - expect { info.send(:info_formula, main_formula) } + expect { info.info_formula(main_formula) } .to output(Regexp.new( "==> Installed Kegs and Versions\n" \ ".*testball\\b.*\\s+1\\.0\\b.*\\(.*\\)\n" \ @@ -1536,7 +1536,7 @@ RSpec.describe Homebrew::Cmd::Info do allow(main_formula).to receive(:versioned_formulae).and_return([]) allow(info).to receive(:github_info).with(main_formula).and_return("https://example.com/testball.rb") - expect { info.send(:info_formula, main_formula) } + expect { info.info_formula(main_formula) } .to not_to_output(/==> Installed Versions\b/).to_stdout .and not_to_output.to_stderr end @@ -1554,7 +1554,7 @@ RSpec.describe Homebrew::Cmd::Info do url "https://brew.sh/testball-0.1.tar.gz" end - expect(described_class.new([]).send(:github_info, formula_instance)) + expect(described_class.new([]).github_info(formula_instance)) .to eq(keg_formula_path.to_s) end @@ -1565,7 +1565,7 @@ RSpec.describe Homebrew::Cmd::Info do url "https://brew.sh/testball-0.1.tar.gz" end - expect(described_class.new([]).send(:github_info, formula_instance)) + expect(described_class.new([]).github_info(formula_instance)) .to eq("https://github.com/Homebrew/homebrew-core/blob/HEAD/" \ "#{formula_path.relative_path_from(tap.path)}") end diff --git a/Library/Homebrew/test/cmd/outdated_spec.rb b/Library/Homebrew/test/cmd/outdated_spec.rb index 8b7e1048f2..ede984a307 100644 --- a/Library/Homebrew/test/cmd/outdated_spec.rb +++ b/Library/Homebrew/test/cmd/outdated_spec.rb @@ -48,7 +48,7 @@ RSpec.describe Homebrew::Cmd::Outdated do expect(cask).to receive(:outdated?) .with(greedy: false, greedy_latest: false, greedy_auto_updates: false) .and_return(false) - expect(cmd.send(:select_outdated, [cask])).to be_empty + expect(cmd.select_outdated([cask])).to be_empty end it "checks auto-updating casks with --greedy-auto-updates", :cask do @@ -58,7 +58,7 @@ RSpec.describe Homebrew::Cmd::Outdated do expect(cask).to receive(:outdated?) .with(greedy: false, greedy_latest: false, greedy_auto_updates: true) .and_return(true) - expect(cmd.send(:select_outdated, [cask])).to eq([cask]) + expect(cmd.select_outdated([cask])).to eq([cask]) end it "excludes auto-updating casks when auto-update upgrades are disabled", :cask do diff --git a/Library/Homebrew/test/cmd/search_spec.rb b/Library/Homebrew/test/cmd/search_spec.rb index fe475fc0fa..7200b934fc 100644 --- a/Library/Homebrew/test/cmd/search_spec.rb +++ b/Library/Homebrew/test/cmd/search_spec.rb @@ -29,7 +29,7 @@ RSpec.describe Homebrew::Cmd::SearchCmd do before { allow_any_instance_of(StringIO).to receive(:tty?).and_return(false) } it "skips" do - expect { search_cmd.send(:print_missing_formula_help, "formula", false) } + expect { search_cmd.print_missing_formula_help("formula", false) } .not_to output.to_stdout end end @@ -38,25 +38,25 @@ RSpec.describe Homebrew::Cmd::SearchCmd do before { allow_any_instance_of(StringIO).to receive(:tty?).and_return(true) } it "skips a regex query" do - expect { search_cmd.send(:print_missing_formula_help, "/formula/", false) } + expect { search_cmd.print_missing_formula_help("/formula/", false) } .not_to output.to_stdout end it "skips if there is not a reason" do allow(Homebrew::MissingFormula).to receive(:reason).and_return(nil) - expect { search_cmd.send(:print_missing_formula_help, "formula", false) } + expect { search_cmd.print_missing_formula_help("formula", false) } .not_to output.to_stdout end it "prints additional output if `found_matches` is true" do allow(Homebrew::MissingFormula).to receive(:reason).and_return("Reason") - expect { search_cmd.send(:print_missing_formula_help, "formula", true) } + expect { search_cmd.print_missing_formula_help("formula", true) } .to output("\nIf you meant \"formula\" specifically:\nReason\n").to_stdout end it "only prints reason if `found_matches` is false" do allow(Homebrew::MissingFormula).to receive(:reason).and_return("Reason") - expect { search_cmd.send(:print_missing_formula_help, "formula", false) } + expect { search_cmd.print_missing_formula_help("formula", false) } .to output("Reason\n").to_stdout end end diff --git a/Library/Homebrew/test/cmd/shared_examples/args_parse.rb b/Library/Homebrew/test/cmd/shared_examples/args_parse.rb index cb84ec0fda..ba4c0f7d90 100644 --- a/Library/Homebrew/test/cmd/shared_examples/args_parse.rb +++ b/Library/Homebrew/test/cmd/shared_examples/args_parse.rb @@ -17,7 +17,7 @@ RSpec.shared_examples "parseable arguments" do |command_name: nil| klass = Object.const_get(command) # rubocop:enable Sorbet/ConstantsFromStrings end - argv = klass.parser.instance_variable_get(:@min_named_args)&.times&.map { "argument" } || [] + argv = klass.parser.min_named_args&.times&.map { "argument" } || [] cmd = klass.new(argv) expect(cmd.args).to be_a Homebrew::CLI::Args end diff --git a/Library/Homebrew/test/cmd/source_spec.rb b/Library/Homebrew/test/cmd/source_spec.rb index 18c733fc0d..f3cc4d9fa8 100644 --- a/Library/Homebrew/test/cmd/source_spec.rb +++ b/Library/Homebrew/test/cmd/source_spec.rb @@ -16,85 +16,85 @@ RSpec.describe Homebrew::Cmd::Source do describe "#github_repo_url" do it "extracts repository URL from GitHub URL" do - expect(described_class.new([]).send(:github_repo_url, "https://github.com/Homebrew/brew.git")) + expect(described_class.new([]).github_repo_url("https://github.com/Homebrew/brew.git")) .to eq("https://github.com/Homebrew/brew") end it "handles GitHub archive URLs" do - expect(described_class.new([]).send(:github_repo_url, "https://github.com/Homebrew/testball/archive/refs/tags/v0.1.tar.gz")) + expect(described_class.new([]).github_repo_url("https://github.com/Homebrew/testball/archive/refs/tags/v0.1.tar.gz")) .to eq("https://github.com/Homebrew/testball") end it "returns nil for non-GitHub URLs" do - expect(described_class.new([]).send(:github_repo_url, "https://example.com/repo.git")) + expect(described_class.new([]).github_repo_url("https://example.com/repo.git")) .to be_nil end end describe "#gitlab_repo_url" do it "extracts repository URL from GitLab URL with nested groups" do - expect(described_class.new([]).send(:gitlab_repo_url, "https://gitlab.com/group/subgroup/project/-/archive/v1.0/project-v1.0.tar.gz")) + expect(described_class.new([]).gitlab_repo_url("https://gitlab.com/group/subgroup/project/-/archive/v1.0/project-v1.0.tar.gz")) .to eq("https://gitlab.com/group/subgroup/project") end it "handles GitLab .git URLs" do - expect(described_class.new([]).send(:gitlab_repo_url, "https://gitlab.com/user/repo.git")) + expect(described_class.new([]).gitlab_repo_url("https://gitlab.com/user/repo.git")) .to eq("https://gitlab.com/user/repo") end it "returns nil for non-GitLab URLs" do - expect(described_class.new([]).send(:gitlab_repo_url, "https://example.com/repo.git")) + expect(described_class.new([]).gitlab_repo_url("https://example.com/repo.git")) .to be_nil end end describe "#bitbucket_repo_url" do it "extracts repository URL from Bitbucket URL" do - expect(described_class.new([]).send(:bitbucket_repo_url, "https://bitbucket.org/user/repo/get/v1.0.tar.gz")) + expect(described_class.new([]).bitbucket_repo_url("https://bitbucket.org/user/repo/get/v1.0.tar.gz")) .to eq("https://bitbucket.org/user/repo") end it "handles Bitbucket .git URLs" do - expect(described_class.new([]).send(:bitbucket_repo_url, "https://bitbucket.org/user/repo.git")) + expect(described_class.new([]).bitbucket_repo_url("https://bitbucket.org/user/repo.git")) .to eq("https://bitbucket.org/user/repo") end it "returns nil for non-Bitbucket URLs" do - expect(described_class.new([]).send(:bitbucket_repo_url, "https://example.com/repo.git")) + expect(described_class.new([]).bitbucket_repo_url("https://example.com/repo.git")) .to be_nil end end describe "#codeberg_repo_url" do it "extracts repository URL from Codeberg URL" do - expect(described_class.new([]).send(:codeberg_repo_url, "https://codeberg.org/user/repo/archive/v1.0.tar.gz")) + expect(described_class.new([]).codeberg_repo_url("https://codeberg.org/user/repo/archive/v1.0.tar.gz")) .to eq("https://codeberg.org/user/repo") end it "handles Codeberg .git URLs" do - expect(described_class.new([]).send(:codeberg_repo_url, "https://codeberg.org/user/repo.git")) + expect(described_class.new([]).codeberg_repo_url("https://codeberg.org/user/repo.git")) .to eq("https://codeberg.org/user/repo") end it "returns nil for non-Codeberg URLs" do - expect(described_class.new([]).send(:codeberg_repo_url, "https://example.com/repo.git")) + expect(described_class.new([]).codeberg_repo_url("https://example.com/repo.git")) .to be_nil end end describe "#sourcehut_repo_url" do it "extracts repository URL from SourceHut URL" do - expect(described_class.new([]).send(:sourcehut_repo_url, "https://git.sr.ht/~user/repo/archive/v1.0.tar.gz")) + expect(described_class.new([]).sourcehut_repo_url("https://git.sr.ht/~user/repo/archive/v1.0.tar.gz")) .to eq("https://sr.ht/~user/repo") end it "handles sr.ht URLs without git subdomain" do - expect(described_class.new([]).send(:sourcehut_repo_url, "https://sr.ht/~user/repo")) + expect(described_class.new([]).sourcehut_repo_url("https://sr.ht/~user/repo")) .to eq("https://sr.ht/~user/repo") end it "returns nil for non-SourceHut URLs" do - expect(described_class.new([]).send(:sourcehut_repo_url, "https://example.com/repo.git")) + expect(described_class.new([]).sourcehut_repo_url("https://example.com/repo.git")) .to be_nil end end @@ -118,8 +118,9 @@ RSpec.describe Homebrew::Cmd::Source do ]) expect(described_class.new([]) - .send(:pypi_repo_url, - "https://files.pythonhosted.org/packages/24/62/ae72ff66c0f1fd959925b4c11f8c2dea61f47f6acaea75a08512cdfe3fed/numpy-2.4.1.tar.gz")) + .pypi_repo_url( + "https://files.pythonhosted.org/packages/24/62/ae72ff66c0f1fd959925b4c11f8c2dea61f47f6acaea75a08512cdfe3fed/numpy-2.4.1.tar.gz", + )) .to eq("https://github.com/numpy/numpy") end @@ -139,13 +140,14 @@ RSpec.describe Homebrew::Cmd::Source do ]) expect(described_class.new([]) - .send(:pypi_repo_url, - "https://files.pythonhosted.org/packages/00/00/000000000000000000000000000000000000000000000000000000000000/foobar-0.0.1.tar.gz")) + .pypi_repo_url( + "https://files.pythonhosted.org/packages/00/00/000000000000000000000000000000000000000000000000000000000000/foobar-0.0.1.tar.gz", + )) .to be_nil end it "returns nil for non-PyPI URLs" do - expect(described_class.new([]).send(:pypi_repo_url, "https://example.com/repo.git")) + expect(described_class.new([]).pypi_repo_url("https://example.com/repo.git")) .to be_nil end end @@ -168,7 +170,7 @@ RSpec.describe Homebrew::Cmd::Source do instance_double(Process::Status, success?: true), ]) - expect(described_class.new([]).send(:npm_repo_url, "https://registry.npmjs.org/#{package}/-/vite-1.2.3.tgz")) + expect(described_class.new([]).npm_repo_url("https://registry.npmjs.org/#{package}/-/vite-1.2.3.tgz")) .to eq("https://github.com/vitejs/vite.git") end end @@ -183,29 +185,29 @@ RSpec.describe Homebrew::Cmd::Source do ]) expect(described_class.new([]) - .send(:npm_repo_url, "https://registry.npmjs.org/vite/-/vite-1.2.3.tgz")) + .npm_repo_url("https://registry.npmjs.org/vite/-/vite-1.2.3.tgz")) .to be_nil end it "returns nil for non-npm URLs" do - expect(described_class.new([]).send(:npm_repo_url, "https://example.com/repo.git")) + expect(described_class.new([]).npm_repo_url("https://example.com/repo.git")) .to be_nil end end describe "#url_to_repo" do it "returns GitHub repo URL for GitHub URLs" do - expect(described_class.new([]).send(:url_to_repo, "https://github.com/Homebrew/brew")) + expect(described_class.new([]).url_to_repo("https://github.com/Homebrew/brew")) .to eq("https://github.com/Homebrew/brew") end it "returns GitLab repo URL for GitLab URLs" do - expect(described_class.new([]).send(:url_to_repo, "https://gitlab.com/user/repo.git")) + expect(described_class.new([]).url_to_repo("https://gitlab.com/user/repo.git")) .to eq("https://gitlab.com/user/repo") end it "returns nil for unsupported URLs" do - expect(described_class.new([]).send(:url_to_repo, "https://example.com/repo.tar.gz")) + expect(described_class.new([]).url_to_repo("https://example.com/repo.tar.gz")) .to be_nil end end diff --git a/Library/Homebrew/test/cmd/tap-info_spec.rb b/Library/Homebrew/test/cmd/tap-info_spec.rb index d20972e9ba..b8072a2482 100644 --- a/Library/Homebrew/test/cmd/tap-info_spec.rb +++ b/Library/Homebrew/test/cmd/tap-info_spec.rb @@ -48,7 +48,7 @@ RSpec.describe Homebrew::Cmd::TapInfo do allow(Homebrew::Trust).to receive(:trusted_tap?).with(tap).and_return(true) with_env(HOMEBREW_REQUIRE_TAP_TRUST: "1") do - expect { tap_info.send(:print_tap_info, [tap]) } + expect { tap_info.print_tap_info([tap]) } .to output(%r{thirdparty/foo: Installed\nTrusted\nNo commands/casks/formulae}).to_stdout end end @@ -76,7 +76,7 @@ RSpec.describe Homebrew::Cmd::TapInfo do allow(Homebrew::Trust).to receive(:trusted_tap?).with(tap).and_return(false) with_env(HOMEBREW_REQUIRE_TAP_TRUST: "1") do - expect { tap_info.send(:print_tap_info, [tap]) } + expect { tap_info.print_tap_info([tap]) } .to output(%r{thirdparty/foo: Installed\nUntrusted\nNo commands/casks/formulae}).to_stdout end end @@ -88,7 +88,7 @@ RSpec.describe Homebrew::Cmd::TapInfo do instance_double(Tap, to_hash: { "name" => "user/b" }), ] - expect { described_class.new([]).send(:print_tap_json, taps) } + expect { described_class.new([]).print_tap_json(taps) } .to output(%r{"name":\s*"user/a".*"name":\s*"user/b"}m).to_stdout end end @@ -103,35 +103,35 @@ RSpec.describe Homebrew::Cmd::TapInfo do end it "does not mark an uninstalled formula" do - expect(tap_info.send(:decorate_formula, tap, "missing", installed: false)).not_to include("✘") + expect(tap_info.decorate_formula(tap, "missing", installed: false)).not_to include("✘") end it "marks an installed formula as satisfied" do formula = instance_double(Formula, outdated?: false, deprecated?: false, disabled?: false) allow(Formulary).to receive(:factory).with("homebrew/foo/installed").and_return(formula) - expect(tap_info.send(:decorate_formula, tap, "installed", installed: true)).to match(/installed.*✔/) + expect(tap_info.decorate_formula(tap, "installed", installed: true)).to match(/installed.*✔/) end it "marks an outdated installed formula as upgradable" do formula = instance_double(Formula, outdated?: true, deprecated?: false, disabled?: false) allow(Formulary).to receive(:factory).with("homebrew/foo/outdated").and_return(formula) - expect(tap_info.send(:decorate_formula, tap, "outdated", installed: true)).to match(/outdated.*↑/) + expect(tap_info.decorate_formula(tap, "outdated", installed: true)).to match(/outdated.*↑/) end it "marks a deprecated formula with `(deprecated)`" do formula = instance_double(Formula, outdated?: false, deprecated?: true, disabled?: false) allow(Formulary).to receive(:factory).with("homebrew/foo/old").and_return(formula) - expect(tap_info.send(:decorate_formula, tap, "old", installed: false)).to match(/old.*\(deprecated\)/) + expect(tap_info.decorate_formula(tap, "old", installed: false)).to match(/old.*\(deprecated\)/) end it "marks a disabled formula with `(disabled)`" do formula = instance_double(Formula, outdated?: false, deprecated?: false, disabled?: true) allow(Formulary).to receive(:factory).with("homebrew/foo/gone").and_return(formula) - expect(tap_info.send(:decorate_formula, tap, "gone", installed: false)).to match(/gone.*\(disabled\)/) + expect(tap_info.decorate_formula(tap, "gone", installed: false)).to match(/gone.*\(disabled\)/) end end @@ -145,35 +145,35 @@ RSpec.describe Homebrew::Cmd::TapInfo do end it "does not mark an uninstalled cask" do - expect(tap_info.send(:decorate_cask, tap, "missing", installed: false)).not_to include("✘") + expect(tap_info.decorate_cask(tap, "missing", installed: false)).not_to include("✘") end it "marks an installed cask as satisfied" do cask = instance_double(Cask::Cask, outdated?: false, deprecated?: false, disabled?: false) allow(Cask::CaskLoader).to receive(:load).with("homebrew/foo/installed").and_return(cask) - expect(tap_info.send(:decorate_cask, tap, "installed", installed: true)).to match(/installed.*✔/) + expect(tap_info.decorate_cask(tap, "installed", installed: true)).to match(/installed.*✔/) end it "marks an outdated installed cask as upgradable" do cask = instance_double(Cask::Cask, outdated?: true, deprecated?: false, disabled?: false) allow(Cask::CaskLoader).to receive(:load).with("homebrew/foo/outdated").and_return(cask) - expect(tap_info.send(:decorate_cask, tap, "outdated", installed: true)).to match(/outdated.*↑/) + expect(tap_info.decorate_cask(tap, "outdated", installed: true)).to match(/outdated.*↑/) end it "marks a deprecated cask with `(deprecated)`" do cask = instance_double(Cask::Cask, outdated?: false, deprecated?: true, disabled?: false) allow(Cask::CaskLoader).to receive(:load).with("homebrew/foo/old").and_return(cask) - expect(tap_info.send(:decorate_cask, tap, "old", installed: false)).to match(/old.*\(deprecated\)/) + expect(tap_info.decorate_cask(tap, "old", installed: false)).to match(/old.*\(deprecated\)/) end it "marks a disabled cask with `(disabled)`" do cask = instance_double(Cask::Cask, outdated?: false, deprecated?: false, disabled?: true) allow(Cask::CaskLoader).to receive(:load).with("homebrew/foo/gone").and_return(cask) - expect(tap_info.send(:decorate_cask, tap, "gone", installed: false)).to match(/gone.*\(disabled\)/) + expect(tap_info.decorate_cask(tap, "gone", installed: false)).to match(/gone.*\(disabled\)/) end end @@ -218,11 +218,11 @@ RSpec.describe Homebrew::Cmd::TapInfo do end it "lists every formula and cask, marking only the installed ones" do - expect { tap_info.send(:print_tap_listings, tap) } + expect { tap_info.print_tap_listings(tap) } .to output( /Commands.*mycmd.*==> Formulae.*foo.*✔.*uninstalled-formula.*==> Casks.*bar.*✔.*uninstalled-cask/m, ).to_stdout - expect { tap_info.send(:print_tap_listings, tap) }.not_to output(/✘/).to_stdout + expect { tap_info.print_tap_listings(tap) }.not_to output(/✘/).to_stdout end end @@ -249,7 +249,7 @@ RSpec.describe Homebrew::Cmd::TapInfo do end it "warns about truncation and shows only installed entries under the standard header" do - expect { tap_info.send(:print_tap_listings, tap) } + expect { tap_info.print_tap_listings(tap) } .to output(/==> Formulae.*formula7.*✔/m).to_stdout .and output(/Tap has more than 30 formulae; showing only installed entries\./).to_stderr end @@ -273,9 +273,9 @@ RSpec.describe Homebrew::Cmd::TapInfo do end it "lists every formula and cask without uninstalled markers" do - expect { tap_info.send(:print_tap_listings, tap) } + expect { tap_info.print_tap_listings(tap) } .to output(/==> Formulae.*baz.*foo.*==> Casks.*bar/m).to_stdout - expect { tap_info.send(:print_tap_listings, tap) }.not_to output(/✘/).to_stdout + expect { tap_info.print_tap_listings(tap) }.not_to output(/✘/).to_stdout end end @@ -298,13 +298,13 @@ RSpec.describe Homebrew::Cmd::TapInfo do end it "shows a link to the tap remote and warns when nothing is installed" do - expect { tap_info.send(:print_tap_listings, tap) } + expect { tap_info.print_tap_listings(tap) } .to output(%r{See: https://github.com/homebrew/homebrew-foo}).to_stdout .and output(/Tap has more than 30 formulae and none are installed\./).to_stderr end it "does not list individual formula names" do - expect { tap_info.send(:print_tap_listings, tap) } + expect { tap_info.print_tap_listings(tap) } .not_to output(/formula1\b/).to_stdout end end diff --git a/Library/Homebrew/test/cmd/update-report_spec.rb b/Library/Homebrew/test/cmd/update-report_spec.rb index acf72cd099..27a75f3430 100644 --- a/Library/Homebrew/test/cmd/update-report_spec.rb +++ b/Library/Homebrew/test/cmd/update-report_spec.rb @@ -15,7 +15,7 @@ RSpec.describe Homebrew::Cmd::UpdateReport do it "links to the donations section" do allow(Homebrew::Settings).to receive(:read).with("donationmessage").and_return("false") - expect { described_class.new([]).send(:donation_message) } + expect { described_class.new([]).donation_message } .to output(include("https://github.com/Homebrew/brew#-donations")).to_stdout end @@ -471,7 +471,7 @@ RSpec.describe Homebrew::Cmd::UpdateReport do expect(hub.select_formula_or_cask(:A)).to be_empty expect(hub.select_formula_or_cask(:D)).to be_empty - expect(hub.instance_variable_get(:@hash)[:R]).to eq([["cv", "progress"]]) + expect(hub.renamed_formulae).to eq([["cv", "progress"]]) end context "when updating a Tap other than the core Tap" do @@ -490,7 +490,7 @@ RSpec.describe Homebrew::Cmd::UpdateReport do expect(hub.select_formula_or_cask(:A)).to be_empty expect(hub.select_formula_or_cask(:D)).to be_empty - expect(hub.instance_variable_get(:@hash)[:R]).to be_nil + expect(hub.renamed_formulae).to be_empty end specify "with renamed Formula and restructured Tap" do @@ -499,7 +499,7 @@ RSpec.describe Homebrew::Cmd::UpdateReport do expect(hub.select_formula_or_cask(:A)).to be_empty expect(hub.select_formula_or_cask(:D)).to be_empty - expect(hub.instance_variable_get(:@hash)[:R]).to eq([%w[foo/bar/xchat foo/bar/xchat2]]) + expect(hub.renamed_formulae).to eq([%w[foo/bar/xchat foo/bar/xchat2]]) end specify "with simulated 'homebrew/php' restructuring" do @@ -507,7 +507,7 @@ RSpec.describe Homebrew::Cmd::UpdateReport do expect(hub.select_formula_or_cask(:A)).to be_empty expect(hub.select_formula_or_cask(:D)).to be_empty - expect(hub.instance_variable_get(:@hash)[:R]).to be_nil + expect(hub.renamed_formulae).to be_empty end specify "with Formula changes" do @@ -515,7 +515,7 @@ RSpec.describe Homebrew::Cmd::UpdateReport do expect(hub.select_formula_or_cask(:A)).to eq(%w[foo/bar/lua]) expect(hub.select_formula_or_cask(:M)).to eq(%w[foo/bar/git]) - expect(hub.instance_variable_get(:@hash)[:R]).to be_nil + expect(hub.renamed_formulae).to be_empty end specify "with formula migrated to cask in same tap" do @@ -545,19 +545,19 @@ RSpec.describe Homebrew::Cmd::UpdateReport do it "recommends trusting just the migrated package then migrating a rename" do expect(other_tap).not_to receive(:ensure_installed!) - expect { reporter.send(:ensure_trusted_tap_installed!, "oldfoo", "newfoo", other_tap) } + expect { reporter.ensure_trusted_tap_installed!("oldfoo", "newfoo", other_tap) } .to output(%r{brew trust foo/bar/newfoo.*brew migrate oldfoo}m).to_stderr end it "recommends a reinstall for an unchanged-name tap migration" do - expect { reporter.send(:ensure_trusted_tap_installed!, "foo", "foo", other_tap) } + expect { reporter.ensure_trusted_tap_installed!("foo", "foo", other_tap) } .to output(/brew reinstall foo/).to_stderr end it "taps a trusted tap" do allow(other_tap).to receive(:official?).and_return(true) expect(other_tap).to receive(:ensure_installed!) - reporter.send(:ensure_trusted_tap_installed!, "foo", "foo", other_tap) + reporter.ensure_trusted_tap_installed!("foo", "foo", other_tap) end end @@ -577,7 +577,7 @@ RSpec.describe Homebrew::Cmd::UpdateReport do -baz DIFF - expect(reporter.send(:diff)).to eq(<<~DIFF.strip) + expect(reporter.diff).to eq(<<~DIFF.strip) A api/bar.rb D api/baz.rb DIFF @@ -593,7 +593,7 @@ RSpec.describe Homebrew::Cmd::UpdateReport do -baz DIFF - expect(reporter.send(:diff)).to eq(<<~DIFF.strip) + expect(reporter.diff).to eq(<<~DIFF.strip) A api/baz.rb DIFF end @@ -698,13 +698,14 @@ RSpec.describe Homebrew::Cmd::UpdateReport do end it "merges frozen report arrays" do + allow(hub).to receive(:select_formula_or_cask).and_call_original first_reporter = instance_double(Reporter, report: { A: ["foo"].freeze }) second_reporter = instance_double(Reporter, report: { A: ["bar"] }) hub.add(first_reporter) hub.add(second_reporter) - expect(hub.instance_variable_get(:@hash)[:A]).to eq(%w[foo bar]) + expect(hub.select_formula_or_cask(:A)).to eq(%w[foo bar]) end end end diff --git a/Library/Homebrew/test/cmd/upgrade_spec.rb b/Library/Homebrew/test/cmd/upgrade_spec.rb index ff8d16058a..24c3b84407 100644 --- a/Library/Homebrew/test/cmd/upgrade_spec.rb +++ b/Library/Homebrew/test/cmd/upgrade_spec.rb @@ -362,7 +362,7 @@ RSpec.describe Homebrew::Cmd::UpgradeCmd do allow(Cask::Upgrade).to receive(:upgrade_casks!).and_raise(Cask::CaskError.new("test cask error")) cmd = described_class.new(["--cask"]) - expect { cmd.send(:upgrade_outdated_casks!, []) } + expect { cmd.upgrade_outdated_casks!([]) } .to output(/test cask error/).to_stderr expect(Homebrew).to have_failed @@ -374,7 +374,7 @@ RSpec.describe Homebrew::Cmd::UpgradeCmd do expect(Homebrew::Install).not_to receive(:ask_casks) expect(Cask::Upgrade).to receive(:upgrade_casks!).and_return(true) - cmd.send(:upgrade_outdated_casks!, []) + cmd.upgrade_outdated_casks!([]) end it "passes --no-quit to cask upgrades" do @@ -385,7 +385,7 @@ RSpec.describe Homebrew::Cmd::UpgradeCmd do true end - cmd.send(:upgrade_outdated_casks!, []) + cmd.upgrade_outdated_casks!([]) end it "passes HOMEBREW_NO_UPGRADE_QUIT_CASKS to cask upgrades" do @@ -397,7 +397,7 @@ RSpec.describe Homebrew::Cmd::UpgradeCmd do true end - cmd.send(:upgrade_outdated_casks!, []) + cmd.upgrade_outdated_casks!([]) end end @@ -409,7 +409,7 @@ RSpec.describe Homebrew::Cmd::UpgradeCmd do expect(cmd).to receive(:upgrade_outdated_formulae!) .with([], dry_run: true, show_upgrade_summary: false) .ordered do - cmd.send(:final_upgrade_summary).version_changes << "testball 0.1 -> 0.2" + cmd.final_upgrade_summary.version_changes << "testball 0.1 -> 0.2" true end expect(cmd).to receive(:upgrade_outdated_casks!) @@ -531,7 +531,7 @@ RSpec.describe Homebrew::Cmd::UpgradeCmd do expect(cmd).to receive(:upgrade_outdated_formulae!) .with([formula], dry_run: true, show_upgrade_summary: false) .ordered do - cmd.send(:final_upgrade_summary).version_changes << "testball 0.1 -> 0.2" + cmd.final_upgrade_summary.version_changes << "testball 0.1 -> 0.2" true end allow(cmd).to receive(:show_final_upgrade_summary).and_call_original @@ -560,7 +560,7 @@ RSpec.describe Homebrew::Cmd::UpgradeCmd do allow(formula).to receive_messages(optlinked?: true, opt_prefix: HOMEBREW_PREFIX/"opt/testball", bottle:) allow(Keg).to receive(:new).with(HOMEBREW_PREFIX/"opt/testball").and_return(keg) - expect(cmd.send(:formula_upgrade_descriptions, [formula], include_sizes: true)) + expect(cmd.formula_upgrade_descriptions([formula], include_sizes: true)) .to eq(["testball 0.1 -> 0.2 (500B)"]) end @@ -578,7 +578,7 @@ RSpec.describe Homebrew::Cmd::UpgradeCmd do allow(Keg).to receive(:new).with(HOMEBREW_PREFIX/"opt/testball").and_return(keg) expect(bottle).not_to receive(:fetch_tab) - expect(cmd.send(:formula_upgrade_descriptions, [formula], include_sizes: true)) + expect(cmd.formula_upgrade_descriptions([formula], include_sizes: true)) .to eq(["testball 0.1 -> 0.2"]) end @@ -697,7 +697,7 @@ RSpec.describe Homebrew::Cmd::UpgradeCmd do allow(cmd).to receive(:final_upgrade_summary).and_return(summary) - expect { cmd.send(:show_final_upgrade_summary) }.to output(<<~EOS).to_stdout + expect { cmd.show_final_upgrade_summary }.to output(<<~EOS).to_stdout ==> Would upgrade 2 outdated packages testball 0.1 -> 0.2 (500B) codex 1.0 -> 2.0 @@ -784,7 +784,7 @@ RSpec.describe Homebrew::Cmd::UpgradeCmd do use_prefetched: false, prefetch_names: nil, prefetch_upgrades: nil, **| if dry_run - cmd.send(:final_upgrade_summary).version_changes << "deno 2.7.10 -> 2.7.11" + cmd.final_upgrade_summary.version_changes << "deno 2.7.10 -> 2.7.11" elsif prefetch_only prefetch_names&.replace(["deno"]) prefetch_upgrades&.replace(["deno 2.7.10 -> 2.7.11"]) @@ -937,8 +937,7 @@ RSpec.describe Homebrew::Cmd::UpgradeCmd do expect(cmd).not_to receive(:ofail) expect( - cmd.send( - :prefetch_outdated_casks!, + cmd.prefetch_outdated_casks!( [], download_queue:, prefetch_names:, @@ -1019,7 +1018,7 @@ RSpec.describe Homebrew::Cmd::UpgradeCmd do allow(Homebrew::Upgrade).to receive(:formula_installers).and_return([]) expect do - cmd.send(:formulae_upgrade_context, [formula], show_upgrade_summary: false) + cmd.formulae_upgrade_context([formula], show_upgrade_summary: false) end.to output("==> Downloading bottle manifests\n").to_stdout end @@ -1042,7 +1041,7 @@ RSpec.describe Homebrew::Cmd::UpgradeCmd do allow(Homebrew::Upgrade).to receive(:formula_installers).and_return([]) expect do - cmd.send(:formulae_upgrade_context, [formula], show_upgrade_summary: false) + cmd.formulae_upgrade_context([formula], show_upgrade_summary: false) end.not_to output(/Downloading bottle manifests/).to_stdout end @@ -1116,7 +1115,7 @@ RSpec.describe Homebrew::Cmd::UpgradeCmd do cmd = described_class.new(["--cask", "--dry-run"]) - expect { cmd.send(:upgrade_outdated_casks!, []) } + expect { cmd.upgrade_outdated_casks!([]) } .to not_to_output(/Unexpected method 'discontinued' called during caveats on Cask local-caffeine\./).to_stderr end @@ -1133,7 +1132,7 @@ RSpec.describe Homebrew::Cmd::UpgradeCmd do allow(cmd).to receive(:final_upgrade_summary).and_return(summary) - expect { cmd.send(:show_final_upgrade_summary) }.to output(<<~EOS).to_stdout + expect { cmd.show_final_upgrade_summary }.to output(<<~EOS).to_stdout ==> Upgraded 1 outdated package testball 0.1 -> 0.2 ==> 1 Pinned formula @@ -1188,8 +1187,8 @@ RSpec.describe Homebrew::Cmd::UpgradeCmd do pinned_formulae: [pinned], ) - cmd.send(:record_formula_upgrade_summary, context) - summary = cmd.send(:final_upgrade_summary) + cmd.record_formula_upgrade_summary(context) + summary = cmd.final_upgrade_summary expect(summary.version_changes).to include("testball 0.1 -> 0.2") expect(summary.pinned_formulae).to include("pinnedball 1.0") @@ -1224,9 +1223,9 @@ RSpec.describe Homebrew::Cmd::UpgradeCmd do end allow(Homebrew::Upgrade).to receive(:upgrade_dependents) - cmd.send(:upgrade_outdated_formulae!, []) + cmd.upgrade_outdated_formulae!([]) - expect(cmd.send(:final_upgrade_summary).version_changes).to include("testball 0.1 -> 0.2") + expect(cmd.final_upgrade_summary.version_changes).to include("testball 0.1 -> 0.2") end it "omits failed formula version changes from the final summary" do @@ -1259,9 +1258,9 @@ RSpec.describe Homebrew::Cmd::UpgradeCmd do allow(Homebrew::Upgrade).to receive(:upgrade_formulae).and_return([successful_formula_installer]) allow(Homebrew::Upgrade).to receive(:upgrade_dependents) - cmd.send(:upgrade_outdated_formulae!, []) + cmd.upgrade_outdated_formulae!([]) - expect(cmd.send(:final_upgrade_summary)).to have_attributes( + expect(cmd.final_upgrade_summary).to have_attributes( version_changes: contain_exactly("testball 0.1 -> 0.2"), deprecated: contain_exactly("failball"), ) diff --git a/Library/Homebrew/test/dependency_expansion_spec.rb b/Library/Homebrew/test/dependency_expansion_spec.rb index b0feeff414..cf056e6700 100644 --- a/Library/Homebrew/test/dependency_expansion_spec.rb +++ b/Library/Homebrew/test/dependency_expansion_spec.rb @@ -174,6 +174,6 @@ RSpec.describe Dependency do allow(foo).to receive(:to_formula).and_raise(FormulaUnavailableError, foo.name) f = instance_double(Formula, name: "f", deps: [foo]) expect { described_class.expand(f) }.to raise_error(FormulaUnavailableError) - expect(described_class.instance_variable_get(:@expand_stack)).to be_empty + expect(described_class.expand_stack).to be_empty end end diff --git a/Library/Homebrew/test/dev-cmd/bump-cask-pr_spec.rb b/Library/Homebrew/test/dev-cmd/bump-cask-pr_spec.rb index ab33662e1b..6bc609bfd6 100644 --- a/Library/Homebrew/test/dev-cmd/bump-cask-pr_spec.rb +++ b/Library/Homebrew/test/dev-cmd/bump-cask-pr_spec.rb @@ -85,12 +85,12 @@ RSpec.describe Homebrew::DevCmd::BumpCaskPr do context "when cask does not have on_system blocks/calls or `depends_on arch`" do it "returns an array only including macOS/ARM" do Homebrew::SimulateSystem.with(os: :linux) do - expect(bump_cask_pr.send(:generate_system_options, c, new_version)) + expect(bump_cask_pr.generate_system_options(c, new_version)) .to eq([[newest_macos, :arm]]) end Homebrew::SimulateSystem.with(os: older_macos) do - expect(bump_cask_pr.send(:generate_system_options, c, new_version)) + expect(bump_cask_pr.generate_system_options(c, new_version)) .to eq([[older_macos, :arm]]) end end @@ -99,12 +99,12 @@ RSpec.describe Homebrew::DevCmd::BumpCaskPr do context "when cask does not have on_system blocks/calls but has `depends_on arch`" do it "returns an array only including macOS/`depends_on arch` value" do Homebrew::SimulateSystem.with(os: :linux, arch: :arm) do - expect(bump_cask_pr.send(:generate_system_options, c_depends_on_intel, new_version)) + expect(bump_cask_pr.generate_system_options(c_depends_on_intel, new_version)) .to eq([[newest_macos, :intel]]) end Homebrew::SimulateSystem.with(os: older_macos, arch: :arm) do - expect(bump_cask_pr.send(:generate_system_options, c_depends_on_intel, new_version)) + expect(bump_cask_pr.generate_system_options(c_depends_on_intel, new_version)) .to eq([[older_macos, :intel]]) end end @@ -113,7 +113,7 @@ RSpec.describe Homebrew::DevCmd::BumpCaskPr do context "when cask has on_system blocks/calls but does not have `depends_on arch`" do it "returns an array with combinations of `OnSystem::BASE_OS_OPTIONS` and `OnSystem::ARCH_OPTIONS`" do Homebrew::SimulateSystem.with(os: :linux) do - expect(bump_cask_pr.send(:generate_system_options, c_on_system, new_version)) + expect(bump_cask_pr.generate_system_options(c_on_system, new_version)) .to eq([ [newest_macos, :intel], [newest_macos, :arm], @@ -123,7 +123,7 @@ RSpec.describe Homebrew::DevCmd::BumpCaskPr do end Homebrew::SimulateSystem.with(os: older_macos) do - expect(bump_cask_pr.send(:generate_system_options, c_on_system, new_version)) + expect(bump_cask_pr.generate_system_options(c_on_system, new_version)) .to eq([ [older_macos, :intel], [older_macos, :arm], @@ -137,7 +137,7 @@ RSpec.describe Homebrew::DevCmd::BumpCaskPr do context "when cask has on_system blocks/calls and `depends_on arch`" do it "returns an array with combinations of `OnSystem::BASE_OS_OPTIONS` and `OnSystem::ARCH_OPTIONS`" do Homebrew::SimulateSystem.with(os: :linux, arch: :arm) do - expect(bump_cask_pr.send(:generate_system_options, c_on_system_depends_on_intel, new_version)) + expect(bump_cask_pr.generate_system_options(c_on_system_depends_on_intel, new_version)) .to eq([ [newest_macos, :intel], [newest_macos, :arm], @@ -147,7 +147,7 @@ RSpec.describe Homebrew::DevCmd::BumpCaskPr do end Homebrew::SimulateSystem.with(os: older_macos, arch: :arm) do - expect(bump_cask_pr.send(:generate_system_options, c_on_system_depends_on_intel, new_version)) + expect(bump_cask_pr.generate_system_options(c_on_system_depends_on_intel, new_version)) .to eq([ [older_macos, :intel], [older_macos, :arm], @@ -177,7 +177,7 @@ RSpec.describe Homebrew::DevCmd::BumpCaskPr do end expect(cask.depends_on.arch).to eq([{ type: :arm, bits: 64 }]) - expect(bump_cask_pr.send(:generate_system_options, cask, new_version)) + expect(bump_cask_pr.generate_system_options(cask, new_version)) .to eq([ [older_macos, :intel], [older_macos, :arm], @@ -196,24 +196,24 @@ RSpec.describe Homebrew::DevCmd::BumpCaskPr do it "returns an array only using archs of arch-specific versions" do Homebrew::SimulateSystem.with(os: :linux) do - expect(bump_cask_pr.send(:generate_system_options, c_arm_intel, new_version_arm)) + expect(bump_cask_pr.generate_system_options(c_arm_intel, new_version_arm)) .to eq([ [newest_macos, :arm], [:linux, :arm], ]) - expect(bump_cask_pr.send(:generate_system_options, c_arm_intel, new_version_intel)) + expect(bump_cask_pr.generate_system_options(c_arm_intel, new_version_intel)) .to eq([ [newest_macos, :intel], [:linux, :intel], ]) - expect(bump_cask_pr.send(:generate_system_options, c_arm_intel, new_version_arm_intel)) + expect(bump_cask_pr.generate_system_options(c_arm_intel, new_version_arm_intel)) .to eq([ [newest_macos, :arm], [newest_macos, :intel], [:linux, :arm], [:linux, :intel], ]) - expect(bump_cask_pr.send(:generate_system_options, c_arm_intel, new_version_intel_arm)) + expect(bump_cask_pr.generate_system_options(c_arm_intel, new_version_intel_arm)) .to eq([ [newest_macos, :intel], [newest_macos, :arm], @@ -223,24 +223,24 @@ RSpec.describe Homebrew::DevCmd::BumpCaskPr do end Homebrew::SimulateSystem.with(os: older_macos) do - expect(bump_cask_pr.send(:generate_system_options, c_arm_intel, new_version_arm)) + expect(bump_cask_pr.generate_system_options(c_arm_intel, new_version_arm)) .to eq([ [older_macos, :arm], [:linux, :arm], ]) - expect(bump_cask_pr.send(:generate_system_options, c_arm_intel, new_version_intel)) + expect(bump_cask_pr.generate_system_options(c_arm_intel, new_version_intel)) .to eq([ [older_macos, :intel], [:linux, :intel], ]) - expect(bump_cask_pr.send(:generate_system_options, c_arm_intel, new_version_arm_intel)) + expect(bump_cask_pr.generate_system_options(c_arm_intel, new_version_arm_intel)) .to eq([ [older_macos, :arm], [older_macos, :intel], [:linux, :arm], [:linux, :intel], ]) - expect(bump_cask_pr.send(:generate_system_options, c_arm_intel, new_version_intel_arm)) + expect(bump_cask_pr.generate_system_options(c_arm_intel, new_version_intel_arm)) .to eq([ [older_macos, :intel], [older_macos, :arm], @@ -274,14 +274,14 @@ RSpec.describe Homebrew::DevCmd::BumpCaskPr do end it "is idempotent when the replacement has already been applied" do - bumped = bump_cask_pr.send(:replace_cask_stanza_value, contents, :version, "1.0", "2.0") + bumped = bump_cask_pr.replace_cask_stanza_value(contents, :version, "1.0", "2.0") expect(bumped).to include('version "2.0"') - expect { bump_cask_pr.send(:replace_cask_stanza_value, bumped, :version, "1.0", "2.0") } + expect { bump_cask_pr.replace_cask_stanza_value(bumped, :version, "1.0", "2.0") } .not_to raise_error end it "raises when the stanza is missing entirely" do - expect { bump_cask_pr.send(:replace_cask_stanza_value, contents, :version, "9.9", "2.0") } + expect { bump_cask_pr.replace_cask_stanza_value(contents, :version, "9.9", "2.0") } .to raise_error(/Could not find 'version' stanza/) end end @@ -317,7 +317,7 @@ RSpec.describe Homebrew::DevCmd::BumpCaskPr do cask = Homebrew::SimulateSystem.with(os: newest_macos, arch: :arm) { cask_from_contents(contents) } new_version = Homebrew::BumpVersionParser.new(arm: "2.0") - expect(bump_cask_pr.send(:replace_version_and_checksum, cask, new_hash, new_version, contents)) + expect(bump_cask_pr.replace_version_and_checksum(cask, new_hash, new_version, contents)) .to eq <<~RUBY cask "foo" do on_arm do @@ -355,7 +355,7 @@ RSpec.describe Homebrew::DevCmd::BumpCaskPr do cask = Homebrew::SimulateSystem.with(os: newest_macos, arch: :arm) { cask_from_contents(contents) } new_version = Homebrew::BumpVersionParser.new(arm: "2.0") - expect(bump_cask_pr.send(:replace_version_and_checksum, cask, new_hash, new_version, contents)) + expect(bump_cask_pr.replace_version_and_checksum(cask, new_hash, new_version, contents)) .to eq <<~RUBY cask "foo" do arch arm: "arm", intel: "intel" @@ -388,7 +388,7 @@ RSpec.describe Homebrew::DevCmd::BumpCaskPr do cask = cask_from_contents(contents) new_version = Homebrew::BumpVersionParser.new(arm: "2.0") - expect(bump_cask_pr.send(:replace_version_and_checksum, cask, new_hash, new_version, contents)) + expect(bump_cask_pr.replace_version_and_checksum(cask, new_hash, new_version, contents)) .to eq <<~RUBY cask "foo" do on_arm do @@ -419,7 +419,7 @@ RSpec.describe Homebrew::DevCmd::BumpCaskPr do new_version = Homebrew::BumpVersionParser.new(arm: "2.0", intel: "1.5") expect( - bump_cask_pr.send(:replace_version_and_checksum, cask, :no_check, new_version, contents), + bump_cask_pr.replace_version_and_checksum(cask, :no_check, new_version, contents), ).to eq <<~RUBY cask "foo" do on_arm do @@ -462,7 +462,7 @@ RSpec.describe Homebrew::DevCmd::BumpCaskPr do new_version = Homebrew::BumpVersionParser.new(arm: "2.0") expect( - bump_cask_pr.send(:replace_version_and_checksum, cask, :no_check, new_version, contents), + bump_cask_pr.replace_version_and_checksum(cask, :no_check, new_version, contents), ).to eq <<~RUBY cask "foo" do on_arm do @@ -502,7 +502,7 @@ RSpec.describe Homebrew::DevCmd::BumpCaskPr do new_version = Homebrew::BumpVersionParser.new(general: "2.0") expect( - bump_cask_pr.send(:replace_version_and_checksum, cask, new_hash, new_version, contents), + bump_cask_pr.replace_version_and_checksum(cask, new_hash, new_version, contents), ).to eq <<~RUBY cask "foo" do on_arm do @@ -544,7 +544,7 @@ RSpec.describe Homebrew::DevCmd::BumpCaskPr do new_version = Homebrew::BumpVersionParser.new(arm: "2.0") expect( - bump_cask_pr.send(:replace_version_and_checksum, cask, :no_check, new_version, contents), + bump_cask_pr.replace_version_and_checksum(cask, :no_check, new_version, contents), ).to eq(contents) end end @@ -602,14 +602,14 @@ RSpec.describe Homebrew::DevCmd::BumpCaskPr do context "when cask is not in a tap" do it "outputs nothing" do - expect { bump_cask_pr.send(:check_throttle, c, new_version:) }.not_to output.to_stderr + expect { bump_cask_pr.check_throttle(c, new_version:) }.not_to output.to_stderr end end context "when a livecheck throttle value isn't present" do it "does not throttle" do allow(c).to receive(:tap).and_return(tap) - expect { bump_cask_pr.send(:check_throttle, c, new_version:) }.not_to output.to_stderr + expect { bump_cask_pr.check_throttle(c, new_version:) }.not_to output.to_stderr end end @@ -623,7 +623,7 @@ RSpec.describe Homebrew::DevCmd::BumpCaskPr do it "does not throttle" do allow(c_throttle).to receive(:tap).and_return(tap) expect do - bump_cask_pr.send(:check_throttle, c_throttle, new_version: empty_version) + bump_cask_pr.check_throttle(c_throttle, new_version: empty_version) end.not_to output.to_stderr end end @@ -632,7 +632,7 @@ RSpec.describe Homebrew::DevCmd::BumpCaskPr do it "does not throttle" do allow(c_throttle).to receive(:tap).and_return(tap) expect do - bump_cask_pr.send(:check_throttle, c_throttle, new_version:) + bump_cask_pr.check_throttle(c_throttle, new_version:) end.not_to output.to_stderr end end @@ -643,7 +643,7 @@ RSpec.describe Homebrew::DevCmd::BumpCaskPr do it "throttles version" do allow(c_throttle).to receive(:tap).and_return(tap) expect do - bump_cask_pr.send(:check_throttle, c_throttle, new_version: new_version_indivisible) + bump_cask_pr.check_throttle(c_throttle, new_version: new_version_indivisible) rescue SystemExit next end.to output(throttle_error).to_stderr @@ -661,7 +661,7 @@ RSpec.describe Homebrew::DevCmd::BumpCaskPr do allow(Homebrew::Livecheck).to receive(:throttle_interval_elapsed?).and_return(false) expect do - bump_cask_pr.send(:check_throttle, c_throttle_rate_and_days, new_version: new_version_indivisible) + bump_cask_pr.check_throttle(c_throttle_rate_and_days, new_version: new_version_indivisible) rescue SystemExit next end.to output(throttle_rate_days_error).to_stderr @@ -671,7 +671,7 @@ RSpec.describe Homebrew::DevCmd::BumpCaskPr do allow(Homebrew::Livecheck).to receive(:throttle_interval_elapsed?).and_return(true) expect do - bump_cask_pr.send(:check_throttle, c_throttle_rate_and_days, new_version: new_version_indivisible) + bump_cask_pr.check_throttle(c_throttle_rate_and_days, new_version: new_version_indivisible) end.not_to output.to_stderr end end @@ -685,7 +685,7 @@ RSpec.describe Homebrew::DevCmd::BumpCaskPr do allow(Homebrew::Livecheck).to receive(:throttle_interval_elapsed?).and_return(false) expect do - bump_cask_pr.send(:check_throttle, c_throttle_days, new_version:) + bump_cask_pr.check_throttle(c_throttle_days, new_version:) rescue SystemExit next end.to output(throttle_days_error).to_stderr @@ -695,7 +695,7 @@ RSpec.describe Homebrew::DevCmd::BumpCaskPr do allow(Homebrew::Livecheck).to receive(:throttle_interval_elapsed?).and_return(true) expect do - bump_cask_pr.send(:check_throttle, c_throttle_days, new_version:) + bump_cask_pr.check_throttle(c_throttle_days, new_version:) end.not_to output.to_stderr end end diff --git a/Library/Homebrew/test/dev-cmd/bump-formula-pr_spec.rb b/Library/Homebrew/test/dev-cmd/bump-formula-pr_spec.rb index 2214a951de..56bdb6d62e 100644 --- a/Library/Homebrew/test/dev-cmd/bump-formula-pr_spec.rb +++ b/Library/Homebrew/test/dev-cmd/bump-formula-pr_spec.rb @@ -108,7 +108,7 @@ RSpec.describe Homebrew::DevCmd::BumpFormulaPr do it "outputs nothing" do allow(f).to receive(:tap).and_return(nil) - expect { bump_formula_pr.send(:check_throttle, f, "1.2.4") }.not_to output.to_stderr + expect { bump_formula_pr.check_throttle(f, "1.2.4") }.not_to output.to_stderr end end @@ -116,7 +116,7 @@ RSpec.describe Homebrew::DevCmd::BumpFormulaPr do it "does not throttle" do allow(f).to receive(:tap).and_return(tap) - expect { bump_formula_pr.send(:check_throttle, f, "1.2.4") }.not_to output.to_stderr + expect { bump_formula_pr.check_throttle(f, "1.2.4") }.not_to output.to_stderr end end @@ -124,7 +124,7 @@ RSpec.describe Homebrew::DevCmd::BumpFormulaPr do it "does not throttle" do allow(f_throttle).to receive(:tap).and_return(tap) - expect { bump_formula_pr.send(:check_throttle, f_throttle, "1.2.5") }.not_to output.to_stderr + expect { bump_formula_pr.check_throttle(f_throttle, "1.2.5") }.not_to output.to_stderr end end @@ -133,7 +133,7 @@ RSpec.describe Homebrew::DevCmd::BumpFormulaPr do allow(f_throttle).to receive(:tap).and_return(tap) expect do - bump_formula_pr.send(:check_throttle, f_throttle, "1.2.4") + bump_formula_pr.check_throttle(f_throttle, "1.2.4") rescue SystemExit nil end.to output(throttle_error).to_stderr @@ -149,7 +149,7 @@ RSpec.describe Homebrew::DevCmd::BumpFormulaPr do allow(Homebrew::Livecheck).to receive(:throttle_interval_elapsed?).and_return(false) expect do - bump_formula_pr.send(:check_throttle, f_throttle_rate_and_days, "1.2.4") + bump_formula_pr.check_throttle(f_throttle_rate_and_days, "1.2.4") rescue SystemExit nil end.to output(throttle_rate_days_error).to_stderr @@ -158,7 +158,7 @@ RSpec.describe Homebrew::DevCmd::BumpFormulaPr do it "does not throttle when throttle interval has elapsed" do allow(Homebrew::Livecheck).to receive(:throttle_interval_elapsed?).and_return(true) - expect { bump_formula_pr.send(:check_throttle, f_throttle_rate_and_days, "1.2.4") }.not_to output.to_stderr + expect { bump_formula_pr.check_throttle(f_throttle_rate_and_days, "1.2.4") }.not_to output.to_stderr end end @@ -171,7 +171,7 @@ RSpec.describe Homebrew::DevCmd::BumpFormulaPr do allow(Homebrew::Livecheck).to receive(:throttle_interval_elapsed?).and_return(false) expect do - bump_formula_pr.send(:check_throttle, f_throttle_days, "1.2.4") + bump_formula_pr.check_throttle(f_throttle_days, "1.2.4") rescue SystemExit next end.to output(throttle_days_error).to_stderr @@ -181,7 +181,7 @@ RSpec.describe Homebrew::DevCmd::BumpFormulaPr do allow(Homebrew::Livecheck).to receive(:throttle_interval_elapsed?).and_return(true) expect do - bump_formula_pr.send(:check_throttle, f_throttle_days, "1.2.4") + bump_formula_pr.check_throttle(f_throttle_days, "1.2.4") end.not_to output.to_stderr end end @@ -210,13 +210,13 @@ RSpec.describe Homebrew::DevCmd::BumpFormulaPr do it "only updates `:parent` resource" do expect(bump_formula_pr).to receive(:update_resource_block!).with(f, resource, version).and_return(:success) - expect(bump_formula_pr.send(:update_matching_version_resources!, f, version:)).to eq({ "parent" => :success }) + expect(bump_formula_pr.update_matching_version_resources!(f, version:)).to eq({ "parent" => :success }) end it "does not update `:parent` resource if set in `--resource-versions`" do resource_versions = { "parent" => { current_version: "1.2.3", latest_version: version } } expect(bump_formula_pr).not_to receive(:update_resource_block!) - expect(bump_formula_pr.send(:update_matching_version_resources!, f, version:, resource_versions:)).to eq({}) + expect(bump_formula_pr.update_matching_version_resources!(f, version:, resource_versions:)).to eq({}) end end @@ -237,21 +237,21 @@ RSpec.describe Homebrew::DevCmd::BumpFormulaPr do version = "2.1.0" resource_versions = { "foo" => { current_version: "1.2.3", latest_version: version } } expect(bump_formula_pr).to receive(:update_resource_block!).with(f, r, version).and_return(:success) - expect(bump_formula_pr.send(:update_resources!, f, resource_versions:)).to eq({ "foo" => :success }) + expect(bump_formula_pr.update_resources!(f, resource_versions:)).to eq({ "foo" => :success }) end it "downgrades to requested version" do version = "0.1.2" resource_versions = { "foo" => { current_version: "1.2.3", latest_version: version } } expect(bump_formula_pr).to receive(:update_resource_block!).with(f, r, version).and_return(:success) - expect(bump_formula_pr.send(:update_resources!, f, resource_versions:)).to eq({ "foo" => :downgraded }) + expect(bump_formula_pr.update_resources!(f, resource_versions:)).to eq({ "foo" => :downgraded }) end it "returns update failures" do version = "0.1.2" resource_versions = { "foo" => { current_version: "1.2.3", latest_version: version } } expect(bump_formula_pr).to receive(:update_resource_block!).with(f, r, version).and_return(:url_unchanged) - expect(bump_formula_pr.send(:update_resources!, f, resource_versions:)).to eq({ "foo" => :url_unchanged }) + expect(bump_formula_pr.update_resources!(f, resource_versions:)).to eq({ "foo" => :url_unchanged }) end end end diff --git a/Library/Homebrew/test/dev-cmd/bump_spec.rb b/Library/Homebrew/test/dev-cmd/bump_spec.rb index a645903504..2c60ebf71a 100644 --- a/Library/Homebrew/test/dev-cmd/bump_spec.rb +++ b/Library/Homebrew/test/dev-cmd/bump_spec.rb @@ -66,7 +66,7 @@ RSpec.describe Homebrew::DevCmd::Bump do describe "::skip_ineligible_formulae!" do it "prints a legible message for casks using `version :latest`" do - expect { expect(bump.send(:skip_ineligible_formulae!, c_latest)).to be(true) } + expect { expect(bump.skip_ineligible_formulae!(c_latest)).to be(true) } .to output(/Cask uses `version :latest` so `brew bump` cannot check it\./).to_stdout .and not_to_output.to_stderr end @@ -101,52 +101,52 @@ RSpec.describe Homebrew::DevCmd::Bump do ) # Compare the same version types when shared by current/new versions - expect(bump.send(:compare_versions, general_version, general_version, f_basic)).to eq({ + expect(bump.compare_versions(general_version, general_version, f_basic)).to eq({ multiple_versions: { current: false, new: false }, newer_than_upstream: { general: false }, }) - expect(bump.send(:compare_versions, general_version, general_version, c_basic)).to eq({ + expect(bump.compare_versions(general_version, general_version, c_basic)).to eq({ multiple_versions: { current: false, new: false }, newer_than_upstream: { general: false }, }) - expect(bump.send(:compare_versions, arm_intel_version, arm_intel_version, c_basic)).to eq({ + expect(bump.compare_versions(arm_intel_version, arm_intel_version, c_basic)).to eq({ multiple_versions: { current: true, new: true }, newer_than_upstream: { arm: false, intel: false }, }) # Compare current versions to new version when the current version differs # by arch but the new version does not - expect(bump.send(:compare_versions, arm_intel_version, general_version, c_basic)).to eq({ + expect(bump.compare_versions(arm_intel_version, general_version, c_basic)).to eq({ multiple_versions: { current: true, new: false }, newer_than_upstream: { arm: false, intel: false }, }) # Compare current version to the highest new version when the # current version does not differ by arch but the new version does - expect(bump.send(:compare_versions, general_version, arm_intel_version, c_basic)).to eq({ + expect(bump.compare_versions(general_version, arm_intel_version, c_basic)).to eq({ multiple_versions: { current: false, new: true }, newer_than_upstream: { general: false }, }) - expect(bump.send(:compare_versions, general_version, arm_intel_version_higher, c_basic)).to eq({ + expect(bump.compare_versions(general_version, arm_intel_version_higher, c_basic)).to eq({ multiple_versions: { current: false, new: true }, newer_than_upstream: { general: false }, }) - expect(bump.send(:compare_versions, general_version, arm_version_intel_skipped, c_basic)).to eq({ + expect(bump.compare_versions(general_version, arm_version_intel_skipped, c_basic)).to eq({ multiple_versions: { current: false, new: true }, newer_than_upstream: { general: false }, }) # Default to `false` when the new version is a message rather than a # version - expect(bump.send(:compare_versions, general_version, skipped, c_basic)).to eq({ + expect(bump.compare_versions(general_version, skipped, c_basic)).to eq({ multiple_versions: { current: false, new: false }, newer_than_upstream: { general: false }, }) - expect(bump.send(:compare_versions, general_version, unable_to_get_versions, c_basic)).to eq({ + expect(bump.compare_versions(general_version, unable_to_get_versions, c_basic)).to eq({ multiple_versions: { current: false, new: false }, newer_than_upstream: { general: false }, }) - expect(bump.send(:compare_versions, general_version, unable_to_get_throttled_versions, c_basic)).to eq({ + expect(bump.compare_versions(general_version, unable_to_get_throttled_versions, c_basic)).to eq({ multiple_versions: { current: false, new: false }, newer_than_upstream: { general: false }, }) @@ -189,7 +189,7 @@ RSpec.describe Homebrew::DevCmd::Bump do "--message=Created by `brew bump`", ).and_return(true) - bump.send(:retrieve_and_display_info_and_open_pr, c_basic, "basic-cask", [], ambiguous_cask: false) + bump.retrieve_and_display_info_and_open_pr(c_basic, "basic-cask", [], ambiguous_cask: false) end it "passes arch-specific version arguments when an arch-specific cask moves to one version" do @@ -220,7 +220,7 @@ RSpec.describe Homebrew::DevCmd::Bump do "--message=Created by `brew bump`", ).and_return(true) - bump.send(:retrieve_and_display_info_and_open_pr, c_basic, "basic-cask", [], ambiguous_cask: false) + bump.retrieve_and_display_info_and_open_pr(c_basic, "basic-cask", [], ambiguous_cask: false) end end @@ -269,8 +269,8 @@ RSpec.describe Homebrew::DevCmd::Bump do allow(Cask::CaskLoader).to receive(:load).and_return(c_arm_only) expect(bump).to receive(:livecheck_result).once.and_return(Version.new("1.2.4")) - version_info = bump.send( - :retrieve_versions_by_arch, formula_or_cask: c_arm_only, repositories: [], name: "arm-only-cask" + version_info = bump.retrieve_versions_by_arch( + formula_or_cask: c_arm_only, repositories: [], name: "arm-only-cask", ) expect(version_info.new_version).to eq(Homebrew::BumpVersionParser.new(general: Version.new("1.2.4"))) end @@ -280,8 +280,8 @@ RSpec.describe Homebrew::DevCmd::Bump do allow(Cask::CaskLoader).to receive(:load).and_return(c_intel_only) expect(bump).to receive(:livecheck_result).once.and_return(Version.new("1.2.4")) - version_info = bump.send( - :retrieve_versions_by_arch, formula_or_cask: c_intel_only, repositories: [], name: "intel-only-cask" + version_info = bump.retrieve_versions_by_arch( + formula_or_cask: c_intel_only, repositories: [], name: "intel-only-cask", ) expect(version_info.new_version).to eq(Homebrew::BumpVersionParser.new(general: Version.new("1.2.4"))) end @@ -301,19 +301,19 @@ RSpec.describe Homebrew::DevCmd::Bump do end it "returns false when value is not a `Cask::DSL::Version` or string" do - expect(bump.send(:message?, version)).to be(false) - expect(bump.send(:message?, nil)).to be(false) + expect(bump.message?(version)).to be(false) + expect(bump.message?(nil)).to be(false) end it "returns false when `Cask::DSL::Version` or string is not a message" do - expect(bump.send(:message?, cask_version)).to be(false) - expect(bump.send(:message?, "Not a message string")).to be(false) + expect(bump.message?(cask_version)).to be(false) + expect(bump.message?("Not a message string")).to be(false) end it "returns true when `Cask::DSL::Version` or string is a message" do message_strings.each do |message_string| - expect(bump.send(:message?, Cask::DSL::Version.new(message_string))).to be(true) - expect(bump.send(:message?, message_string)).to be(true) + expect(bump.message?(Cask::DSL::Version.new(message_string))).to be(true) + expect(bump.message?(message_string)).to be(true) end end end @@ -336,21 +336,19 @@ RSpec.describe Homebrew::DevCmd::Bump do it "emits only changed arch arguments when a general cask version becomes arch-specific" do expect( - bump.send(:version_args_for_bump, - current_version: current_general, - new_version: new_split, - multiple_versions: { current: false, new: true }, - name: "foo"), + bump.version_args_for_bump(current_version: current_general, + new_version: new_split, + multiple_versions: { current: false, new: true }, + name: "foo"), ).to eq(["--version-arm=1.2.6"]) end it "emits arch arguments for both architectures when split cask versions merge" do expect( - bump.send(:version_args_for_bump, - current_version: current_split, - new_version: new_general, - multiple_versions: { current: true, new: false }, - name: "foo"), + bump.version_args_for_bump(current_version: current_split, + new_version: new_general, + multiple_versions: { current: true, new: false }, + name: "foo"), ).to eq(["--version-arm=1.2.4", "--version-intel=1.2.4"]) end @@ -361,21 +359,19 @@ RSpec.describe Homebrew::DevCmd::Bump do ) expect( - bump.send(:version_args_for_bump, - current_version: current_split, - new_version: new_split, - multiple_versions: { current: true, new: true }, - name: "foo"), + bump.version_args_for_bump(current_version: current_split, + new_version: new_split, + multiple_versions: { current: true, new: true }, + name: "foo"), ).to eq(["--version-arm=1.2.4"]) end it "keeps existing general version routing" do expect( - bump.send(:version_args_for_bump, - current_version: current_general, - new_version: new_general, - multiple_versions: { current: false, new: false }, - name: "foo"), + bump.version_args_for_bump(current_version: current_general, + new_version: new_general, + multiple_versions: { current: false, new: false }, + name: "foo"), ).to eq(["--version=1.2.4"]) end @@ -386,11 +382,10 @@ RSpec.describe Homebrew::DevCmd::Bump do ) expect( - bump.send(:version_args_for_bump, - current_version: current_general, - new_version: new_split, - multiple_versions: { current: false, new: true }, - name: "foo"), + bump.version_args_for_bump(current_version: current_general, + new_version: new_split, + multiple_versions: { current: false, new: true }, + name: "foo"), ).to eq(["--version-arm=1.2.6"]) end end @@ -441,7 +436,7 @@ RSpec.describe Homebrew::DevCmd::Bump do ) .and_return([content, "", instance_double(Process::Status, success?: true)]) - expect(bump.send(:version_with_cooldown, version_info, Version.new("1.2.2"))).to eq(Version.new("1.2.3")) + expect(bump.version_with_cooldown(version_info, Version.new("1.2.2"))).to eq(Version.new("1.2.3")) end it "uses platform-specific RubyGems releases for native gems" do @@ -501,7 +496,7 @@ RSpec.describe Homebrew::DevCmd::Bump do ) .and_return([content, "", instance_double(Process::Status, success?: true)]) - expect(bump.send(:version_with_cooldown, version_info, Version.new("1.2.2"))).to eq(Version.new("1.2.3")) + expect(bump.version_with_cooldown(version_info, Version.new("1.2.2"))).to eq(Version.new("1.2.3")) end end end diff --git a/Library/Homebrew/test/dev-cmd/contributions_spec.rb b/Library/Homebrew/test/dev-cmd/contributions_spec.rb index 5df169b5a6..a0cb355ae7 100644 --- a/Library/Homebrew/test/dev-cmd/contributions_spec.rb +++ b/Library/Homebrew/test/dev-cmd/contributions_spec.rb @@ -44,7 +44,7 @@ RSpec.describe Homebrew::DevCmd::Contributions do args: ["-C", repository_path, "show", "first-mention^:README.md"], print_stderr: false) .and_return(instance_double(SystemCommand::Result, stdout: "")) - expect(command.send(:maintainer_since, repository_path, "quarter-end-ref", "alice", "Alice")) + expect(command.maintainer_since(repository_path, "quarter-end-ref", "alice", "Alice")) .to eq("2020-01-02") end @@ -64,7 +64,7 @@ RSpec.describe Homebrew::DevCmd::Contributions do MARKDOWN allow(command).to receive(:maintainer_since).and_return("2020-01-02") - expect(command.send(:maintainer_report_users, repository_refs, "2025-09-01")).to eq([ + expect(command.maintainer_report_users(repository_refs, "2025-09-01")).to eq([ { "alice" => "Alice", "bob" => "Bob" }, {}, { "alice" => "2020-01-02", "bob" => "2020-01-02" } ]) end @@ -189,8 +189,7 @@ RSpec.describe Homebrew::DevCmd::Contributions do .with("", is: "merged", user: "Homebrew", author: "alice", merged: "2026-01-01..2026-01-31") .and_return([{ "number" => 123, "repository_url" => "#{GitHub::API_URL}/repos/#{repository}" }]) - results = command.send( - :scan_contributions, + results = command.scan_contributions( "Homebrew", [repository], {}, @@ -214,8 +213,7 @@ RSpec.describe Homebrew::DevCmd::Contributions do .with("", is: "merged", user: "Homebrew", author: "alice", merged: "2026-01-01..2026-01-31") .and_return([{ "number" => 123, "repository_url" => "#{GitHub::API_URL}/repos/Homebrew/homebrew-core" }]) - results = command.send( - :scan_contributions, + results = command.scan_contributions( "Homebrew", repositories, {}, @@ -251,8 +249,7 @@ RSpec.describe Homebrew::DevCmd::Contributions do .and_return(Array.new(25) { |index| { "number" => index + 100 } }) expect(GitHub).not_to receive(:search_approved_pull_requests_in_user_or_organisation) - results = command.send( - :scan_contributions, + results = command.scan_contributions( "Homebrew", repositories.keys, repositories, @@ -281,8 +278,7 @@ RSpec.describe Homebrew::DevCmd::Contributions do .with("Homebrew", "alice", from: "2026-01-01", to: "2026-02-01") .and_return([]) - results = command.send( - :scan_contributions, + results = command.scan_contributions( "Homebrew", [repository], {}, @@ -302,7 +298,7 @@ RSpec.describe Homebrew::DevCmd::Contributions do command = described_class.new(["--user=39449589+krehel@users.noreply.github.com"]) expect(GitHub).not_to receive(:search) - expect(command.send(:github_username_for, "39449589+krehel@users.noreply.github.com", to: "2026-02-01")) + expect(command.github_username_for("39449589+krehel@users.noreply.github.com", to: "2026-02-01")) .to eq("krehel") end @@ -321,8 +317,7 @@ RSpec.describe Homebrew::DevCmd::Contributions do { "number" => 124, "repository_url" => "#{GitHub::API_URL}/repos/#{repository}" }, ]) - results = command.send( - :scan_contributions, + results = command.scan_contributions( "Homebrew", [repository], repository_refs, @@ -356,8 +351,7 @@ RSpec.describe Homebrew::DevCmd::Contributions do .with("", is: "merged", user: "Homebrew", author: "alice", merged: "2026-01-01..2026-01-31") .and_return([{ "number" => 123, "repository_url" => "#{GitHub::API_URL}/repos/#{repository}" }]) - results = command.send( - :scan_contributions, + results = command.scan_contributions( "Homebrew", [repository], repository_refs, @@ -391,8 +385,7 @@ RSpec.describe Homebrew::DevCmd::Contributions do .with("", is: "merged", user: "Homebrew", author: "alice", merged: "2026-01-01..2026-01-31") .and_return([{ "number" => 123, "repository_url" => "#{GitHub::API_URL}/repos/#{repository}" }]) - results = command.send( - :scan_contributions, + results = command.scan_contributions( "Homebrew", [repository], repository_refs, @@ -425,8 +418,7 @@ RSpec.describe Homebrew::DevCmd::Contributions do "Change another thing\n\nCo-authored-by: Bob Example " ].join(separator) - counts = command.send( - :parse_git_log, + counts = command.parse_git_log( "#{merge}#{record_separator}#{pull_request}#{record_separator}#{coauthored}#{record_separator}", { "alice" => "Alice Example", "bob" => "bob" }, ) @@ -457,8 +449,7 @@ RSpec.describe Homebrew::DevCmd::Contributions do .and_return([]) expect(GitHub).not_to receive(:search_approved_pull_requests_in_user_or_organisation) - expect(command.send( - :scan_contributions, + expect(command.scan_contributions( "Homebrew", repositories.keys, repositories, @@ -495,8 +486,7 @@ RSpec.describe Homebrew::DevCmd::Contributions do from: "2025-12-01", to: "2026-03-01") .and_return(Array.new(25) { {} }) - results = command.send( - :scan_contributions, + results = command.scan_contributions( "Homebrew", repositories.keys, repositories, @@ -534,8 +524,7 @@ RSpec.describe Homebrew::DevCmd::Contributions do { "repository_url" => "#{GitHub::API_URL}/repos/Homebrew/homebrew-core" }, ]) - results = command.send( - :scan_contributions, + results = command.scan_contributions( "Homebrew", repositories.keys, repositories, @@ -560,7 +549,7 @@ RSpec.describe Homebrew::DevCmd::Contributions do 2.times do cache_key = %w[approved Homebrew alice 2026-1].join("\0") - expect(command.send(:github_search_with_rate_limit, cache_key, to: "2026-03-01") do + expect(command.github_search_with_rate_limit(cache_key, to: "2026-03-01") do calls += 1 results end).to eq(results) diff --git a/Library/Homebrew/test/dev-cmd/generate-cask-ci-matrix_spec.rb b/Library/Homebrew/test/dev-cmd/generate-cask-ci-matrix_spec.rb index 72d48e7412..e3edaa6d00 100644 --- a/Library/Homebrew/test/dev-cmd/generate-cask-ci-matrix_spec.rb +++ b/Library/Homebrew/test/dev-cmd/generate-cask-ci-matrix_spec.rb @@ -420,7 +420,7 @@ RSpec.describe Homebrew::DevCmd::GenerateCaskCiMatrix do runners.keys.find { |runner| runner.fetch(:symbol) == :linux } || runners.keys.first end - runners, multi_os = generate_matrix.send(:runners, cask: c) + runners, multi_os = generate_matrix.runners(cask: c) expect(runners.map { |runner| [(runner.fetch(:symbol) == :linux) ? :linux : :macos, runner.fetch(:arch)] }) .to contain_exactly([:macos, :arm], [:macos, :intel], [:linux, :arm], [:linux, :intel]) diff --git a/Library/Homebrew/test/dev-cmd/generate-zap_spec.rb b/Library/Homebrew/test/dev-cmd/generate-zap_spec.rb index 31c4a9897f..c013efbb08 100644 --- a/Library/Homebrew/test/dev-cmd/generate-zap_spec.rb +++ b/Library/Homebrew/test/dev-cmd/generate-zap_spec.rb @@ -33,7 +33,7 @@ RSpec.describe Homebrew::DevCmd::GenerateZap do allow(app).to receive(:is_a?).with(Cask::Artifact::App).and_return(true) cask = instance_double(Cask::Cask, artifacts: [app]) - expect(generate_zap.send(:resolve_patterns_from_cask, cask)).to eq(["TestCask"]) + expect(generate_zap.resolve_patterns_from_cask(cask)).to eq(["TestCask"]) end it "resolves bundle identifier from an installed app artifact" do @@ -52,7 +52,7 @@ RSpec.describe Homebrew::DevCmd::GenerateZap do .with("plutil", args: ["-convert", "xml1", "-o", "-", info_plist]) .and_return(result) - expect(generate_zap.send(:resolve_patterns_from_cask, cask)) + expect(generate_zap.resolve_patterns_from_cask(cask)) .to eq(["TestCask", "com.example.testcask"]) end end @@ -60,7 +60,7 @@ RSpec.describe Homebrew::DevCmd::GenerateZap do it "falls back to title-cased token when no app artifact exists" do cask = Cask::Cask.new("test-cask") - expect(generate_zap.send(:resolve_patterns_from_cask, cask)).to eq(["Test Cask"]) + expect(generate_zap.resolve_patterns_from_cask(cask)).to eq(["Test Cask"]) end end @@ -73,8 +73,8 @@ RSpec.describe Homebrew::DevCmd::GenerateZap do allow(Dir).to receive(:home).and_return(tmpdir) - results = generate_zap.send(:scan_directories, ["Library/Preferences"], - home_relative: true, patterns: ["foo"]) + results = generate_zap.scan_directories(["Library/Preferences"], + home_relative: true, patterns: ["foo"]) expect(results.size).to eq(1) expect(results.first).to include("com.example.Foo.plist") @@ -82,8 +82,8 @@ RSpec.describe Homebrew::DevCmd::GenerateZap do end it "returns empty array when directory does not exist" do - results = generate_zap.send(:scan_directories, ["nonexistent/path"], - home_relative: true, patterns: ["test"]) + results = generate_zap.scan_directories(["nonexistent/path"], + home_relative: true, patterns: ["test"]) expect(results).to be_empty end @@ -95,8 +95,8 @@ RSpec.describe Homebrew::DevCmd::GenerateZap do allow(Dir).to receive(:home).and_return(tmpdir) - results = generate_zap.send(:scan_directories, ["Library/Preferences"], - home_relative: true, patterns: ["foo", "bar"]) + results = generate_zap.scan_directories(["Library/Preferences"], + home_relative: true, patterns: ["foo", "bar"]) expect(results.size).to eq(2) end @@ -112,7 +112,7 @@ RSpec.describe Homebrew::DevCmd::GenerateZap do allow(Dir).to receive(:home).and_return(tmpdir) - results = generate_zap.send(:scan_home_root, ["foo"]) + results = generate_zap.scan_home_root(["foo"]) expect(results.size).to eq(1) expect(results.first).to include(".foo") @@ -127,7 +127,7 @@ RSpec.describe Homebrew::DevCmd::GenerateZap do FileUtils.touch("#{tmpdir}/b") entries = [] - generate_zap.send(:each_readable_child, tmpdir) { |entry| entries << entry } + generate_zap.each_readable_child(tmpdir) { |entry| entries << entry } expect(entries).to contain_exactly("a", "b") end @@ -136,7 +136,7 @@ RSpec.describe Homebrew::DevCmd::GenerateZap do it "skips directories that raise a permission error" do allow(Dir).to receive(:each_child).and_raise(Errno::EPERM) - expect { generate_zap.send(:each_readable_child, "/protected") { |_entry| nil } }.not_to raise_error + expect { generate_zap.each_readable_child("/protected") { |_entry| nil } }.not_to raise_error end end @@ -144,7 +144,7 @@ RSpec.describe Homebrew::DevCmd::GenerateZap do it "returns empty array when Info.plist is missing" do app = instance_double(Cask::Artifact::App, target: Pathname.new("TestCask.app")) - expect(generate_zap.send(:bundle_identifiers, app)).to eq([]) + expect(generate_zap.bundle_identifiers(app)).to eq([]) end it "returns empty array when Info.plist is unreadable" do @@ -154,7 +154,7 @@ RSpec.describe Homebrew::DevCmd::GenerateZap do allow(app_path).to receive(:/).with("Contents/Info.plist").and_return(info_plist) - expect(generate_zap.send(:bundle_identifiers, app)).to eq([]) + expect(generate_zap.bundle_identifiers(app)).to eq([]) end it "returns empty array when CFBundleIdentifier is not a string" do @@ -171,7 +171,7 @@ RSpec.describe Homebrew::DevCmd::GenerateZap do .with("plutil", args: ["-convert", "xml1", "-o", "-", info_plist]) .and_return(result) - expect(generate_zap.send(:bundle_identifiers, app)).to eq([]) + expect(generate_zap.bundle_identifiers(app)).to eq([]) end end end @@ -182,7 +182,7 @@ RSpec.describe Homebrew::DevCmd::GenerateZap do "~/Library/Application Scripts/com.example.foo", "~/Library/Application Scripts/com.example.foo.plist", ] - result = generate_zap.send(:collapse_to_wildcards, paths) + result = generate_zap.collapse_to_wildcards(paths) expect(result).to eq(["~/Library/Application Scripts/com.example.foo*"]) end @@ -193,7 +193,7 @@ RSpec.describe Homebrew::DevCmd::GenerateZap do "~/Library/Preferences/com.example.foo.plist", "~/Library/Preferences/com.example.app.plist", ] - result = generate_zap.send(:collapse_to_wildcards, paths) + result = generate_zap.collapse_to_wildcards(paths) expect(result).to include("~/Library/Preferences/com.example.foo*") expect(result).to include("~/Library/Preferences/com.example.app.plist") @@ -202,7 +202,7 @@ RSpec.describe Homebrew::DevCmd::GenerateZap do it "leaves single entries unchanged" do paths = ["~/Library/Caches/com.example.foo"] - result = generate_zap.send(:collapse_to_wildcards, paths) + result = generate_zap.collapse_to_wildcards(paths) expect(result).to eq(paths) end @@ -212,7 +212,7 @@ RSpec.describe Homebrew::DevCmd::GenerateZap do "~/Library/Caches/com.example.foo", "~/Library/Preferences/com.example.foo.plist", ] - result = generate_zap.send(:collapse_to_wildcards, paths) + result = generate_zap.collapse_to_wildcards(paths) expect(result).to eq(paths) end @@ -222,7 +222,7 @@ RSpec.describe Homebrew::DevCmd::GenerateZap do "~/Library/Preferences/com.example.app.plist", "~/Library/Preferences/com.example.foo.plist", ] - result = generate_zap.send(:collapse_to_wildcards, paths) + result = generate_zap.collapse_to_wildcards(paths) expect(result).to eq(paths) end @@ -231,43 +231,40 @@ RSpec.describe Homebrew::DevCmd::GenerateZap do describe "#normalize_path" do it "replaces home directory with ~" do home = Dir.home - expect(generate_zap.send(:normalize_path, "#{home}/Library/Preferences/com.example.foo.plist")) + expect(generate_zap.normalize_path("#{home}/Library/Preferences/com.example.foo.plist")) .to eq("~/Library/Preferences/com.example.foo.plist") end it "leaves system paths unchanged" do - expect(generate_zap.send(:normalize_path, "/Library/Preferences/com.example.foo.plist")) + expect(generate_zap.normalize_path("/Library/Preferences/com.example.foo.plist")) .to eq("/Library/Preferences/com.example.foo.plist") end end describe "#format_stanza" do it "formats a single trash path as inline" do - output = generate_zap.send(:format_stanza, - trash: ["~/Library/Preferences/com.example.foo.plist"], - delete: [], - rmdir: []) + output = generate_zap.format_stanza(trash: ["~/Library/Preferences/com.example.foo.plist"], + delete: [], + rmdir: []) expect(output).to eq('zap trash: "~/Library/Preferences/com.example.foo.plist"') end it "formats multiple trash paths as an array" do - output = generate_zap.send(:format_stanza, - trash: [ - "~/Library/Caches/com.example.foo", - "~/Library/Preferences/com.example.foo.plist", - ], - delete: [], - rmdir: []) + output = generate_zap.format_stanza(trash: [ + "~/Library/Caches/com.example.foo", + "~/Library/Preferences/com.example.foo.plist", + ], + delete: [], + rmdir: []) expect(output).to include("zap trash: [") expect(output).to include('"~/Library/Caches/com.example.foo"') expect(output).to include('"~/Library/Preferences/com.example.foo.plist"') end it "includes multiple directive types" do - output = generate_zap.send(:format_stanza, - trash: ["~/Library/Preferences/com.example.foo.plist"], - delete: ["/Library/Preferences/com.example.foo.plist"], - rmdir: ["~/Library/Application Support/Foo"]) + output = generate_zap.format_stanza(trash: ["~/Library/Preferences/com.example.foo.plist"], + delete: ["/Library/Preferences/com.example.foo.plist"], + rmdir: ["~/Library/Application Support/Foo"]) expect(output).to include("trash:") expect(output).to include("delete:") expect(output).to include("rmdir:") @@ -279,7 +276,7 @@ RSpec.describe Homebrew::DevCmd::GenerateZap do paths = [ "~/Library/Application Support/CrashReporter/Foo_1BBE8750-D851-5930-A16F-BE4B820B4537.plist", ] - result = generate_zap.send(:replace_uuids, paths) + result = generate_zap.replace_uuids(paths) expect(result).to eq(["~/Library/Application Support/CrashReporter/Foo_*.plist"]) end @@ -289,14 +286,14 @@ RSpec.describe Homebrew::DevCmd::GenerateZap do "~/Library/Caches/com.example.foo/Data_1BBE8750-D851-5930-A16F-BE4B820B4537", "~/Library/Caches/com.example.foo/Data_AABBCCDD-1122-3344-5566-778899AABBCC", ] - result = generate_zap.send(:replace_uuids, paths) + result = generate_zap.replace_uuids(paths) expect(result).to eq(["~/Library/Caches/com.example.foo/Data_*"]) end it "leaves paths without UUIDs unchanged" do paths = ["~/Library/Preferences/com.example.foo.plist"] - result = generate_zap.send(:replace_uuids, paths) + result = generate_zap.replace_uuids(paths) expect(result).to eq(paths) end @@ -311,14 +308,14 @@ RSpec.describe Homebrew::DevCmd::GenerateZap do "#{shared_file_list}/org.example.foo.sfl2", "#{shared_file_list}/org.example.foo.sfl3", ] - result = generate_zap.send(:glob_shared_filelists, paths) + result = generate_zap.glob_shared_filelists(paths) expect(result).to eq(["#{shared_file_list}/org.example.foo.sfl*"]) end it "leaves paths without a Shared File List version unchanged" do paths = ["~/Library/Preferences/com.example.foo.plist"] - result = generate_zap.send(:glob_shared_filelists, paths) + result = generate_zap.glob_shared_filelists(paths) expect(result).to eq(paths) end @@ -327,19 +324,19 @@ RSpec.describe Homebrew::DevCmd::GenerateZap do describe "#derive_rmdir_candidates" do it "suggests Application Support parent directories" do paths = ["~/Library/Application Support/Foo/config.json"] - result = generate_zap.send(:derive_rmdir_candidates, paths) + result = generate_zap.derive_rmdir_candidates(paths) expect(result).to include("~/Library/Application Support/Foo") end it "does not suggest rmdir for Preferences" do paths = ["~/Library/Preferences/com.example.foo.plist"] - result = generate_zap.send(:derive_rmdir_candidates, paths) + result = generate_zap.derive_rmdir_candidates(paths) expect(result).to be_empty end it "does not suggest rmdir for CrashReporter" do paths = ["~/Library/Application Support/CrashReporter/Foo_ABC123.plist"] - result = generate_zap.send(:derive_rmdir_candidates, paths) + result = generate_zap.derive_rmdir_candidates(paths) expect(result).to be_empty end @@ -350,13 +347,13 @@ RSpec.describe Homebrew::DevCmd::GenerateZap do paths = [ "#{application_recent_documents}/org.example.foo.sfl2", ] - result = generate_zap.send(:derive_rmdir_candidates, paths) + result = generate_zap.derive_rmdir_candidates(paths) expect(result).not_to include(application_recent_documents) end it "does not suggest rmdir for system-level shared directories" do paths = ["/Library/Application Support/Foo"] - result = generate_zap.send(:derive_rmdir_candidates, paths) + result = generate_zap.derive_rmdir_candidates(paths) expect(result).to be_empty end end diff --git a/Library/Homebrew/test/dev-cmd/release_spec.rb b/Library/Homebrew/test/dev-cmd/release_spec.rb index a704406ee7..a7feef9988 100644 --- a/Library/Homebrew/test/dev-cmd/release_spec.rb +++ b/Library/Homebrew/test/dev-cmd/release_spec.rb @@ -70,12 +70,12 @@ RSpec.describe Homebrew::DevCmd::Release do end it "filters releases by name or tag name" do - matching = command.send(:matching_releases, "1.2.3") + matching = command.matching_releases("1.2.3") expect(matching.map { |release| release["id"] }).to eq([1, 2, 4]) end it "selects the latest matching release by creation time" do - latest = command.send(:latest_matching_release, "1.2.3") + latest = command.latest_matching_release("1.2.3") expect(latest["id"]).to eq(4) end end diff --git a/Library/Homebrew/test/dev-cmd/tests_spec.rb b/Library/Homebrew/test/dev-cmd/tests_spec.rb index 0cc93e85bc..b1bfc7077a 100644 --- a/Library/Homebrew/test/dev-cmd/tests_spec.rb +++ b/Library/Homebrew/test/dev-cmd/tests_spec.rb @@ -23,7 +23,7 @@ RSpec.describe Homebrew::DevCmd::Tests do expect(Sandbox).not_to receive(:configure!) with_env(HOMEBREW_NO_SANDBOX_LINUX: "1") do - expect { tests.send(:check_test_environment!) }.not_to raise_error + expect { tests.check_test_environment! }.not_to raise_error end end @@ -32,7 +32,7 @@ RSpec.describe Homebrew::DevCmd::Tests do failure_reason: "Bubblewrap is not working.") expect(Sandbox).to receive(:ensure_sandbox_installed!).with(install_from_tests: true) - expect { tests.send(:check_test_environment!) } + expect { tests.check_test_environment! } .to raise_error(RuntimeError, "Bubblewrap is not working.") end @@ -40,7 +40,7 @@ RSpec.describe Homebrew::DevCmd::Tests do allow(Sandbox).to receive(:available?).and_return(true) expect(Sandbox).to receive(:ensure_sandbox_installed!).with(install_from_tests: true) - expect { tests.send(:check_test_environment!) }.not_to raise_error + expect { tests.check_test_environment! }.not_to raise_error end it "configures the sandbox on GitHub Actions when Linux sandboxing is enabled" do @@ -49,7 +49,7 @@ RSpec.describe Homebrew::DevCmd::Tests do expect(Sandbox).to receive(:configure!) expect(Sandbox).not_to receive(:ensure_sandbox_installed!) - expect { tests.send(:check_test_environment!) }.not_to raise_error + expect { tests.check_test_environment! }.not_to raise_error end end @@ -82,7 +82,7 @@ RSpec.describe Homebrew::DevCmd::Tests do end describe "#changed_test_files" do - subject(:changed_test_files) { tests.send(:changed_test_files) } + subject(:changed_test_files) { tests.changed_test_files } let(:tests) { described_class.new([]) } diff --git a/Library/Homebrew/test/download_queue_spec.rb b/Library/Homebrew/test/download_queue_spec.rb index 9ab062d9d9..8ba7cb1e9a 100644 --- a/Library/Homebrew/test/download_queue_spec.rb +++ b/Library/Homebrew/test/download_queue_spec.rb @@ -47,7 +47,7 @@ RSpec.describe Homebrew::DownloadQueue do download_queue.enqueue(downloadable) expect { download_queue.fetch }.to raise_error(Resource::BottleManifest::Error, /manifest missing/) - expect(download_queue.send(:downloads)).to be_empty + expect(download_queue.downloads).to be_empty end it "cancels remaining downloads and raises on a bottle manifest failure in serial mode" do diff --git a/Library/Homebrew/test/download_strategies/abstract_file_spec.rb b/Library/Homebrew/test/download_strategies/abstract_file_spec.rb index 2fb96fb813..4ad97d604b 100644 --- a/Library/Homebrew/test/download_strategies/abstract_file_spec.rb +++ b/Library/Homebrew/test/download_strategies/abstract_file_spec.rb @@ -10,30 +10,30 @@ RSpec.describe AbstractFileDownloadStrategy do describe "#parse_basename" do it "returns the final path segment for simple URLs" do - expect(strategy.send(:parse_basename, "https://example.com/foo.tar.gz")).to eq("foo.tar.gz") + expect(strategy.parse_basename("https://example.com/foo.tar.gz")).to eq("foo.tar.gz") end it "prefers a path segment with an extension over later extensionless segments" do - expect(strategy.send(:parse_basename, "https://example.com/foo-1.0.tar.gz/download")).to eq("foo-1.0.tar.gz") + expect(strategy.parse_basename("https://example.com/foo-1.0.tar.gz/download")).to eq("foo-1.0.tar.gz") end it "extracts the basename from a response-content-disposition query parameter" do url = "https://example.com/download.php?file=ignored&response-content-disposition=attachment;filename=\"real.tar.gz\"" - expect(strategy.send(:parse_basename, url)).to eq("real.tar.gz") + expect(strategy.parse_basename(url)).to eq("real.tar.gz") end it "uses the query value when the path has no extension" do url = "https://example.com/download.php?file=foo-1.0.tar.gz" - expect(strategy.send(:parse_basename, url)).to eq("foo-1.0.tar.gz") + expect(strategy.parse_basename(url)).to eq("foo-1.0.tar.gz") end it "returns the final segment for file:// URLs even when an ancestor directory contains a dot" do - expect(strategy.send(:parse_basename, "file:///Users/me/git-repos/github.com/Homebrew/brew/naked_executable")) + expect(strategy.parse_basename("file:///Users/me/git-repos/github.com/Homebrew/brew/naked_executable")) .to eq("naked_executable") end it "returns the final segment for file:// URLs with an extension" do - expect(strategy.send(:parse_basename, "file:///tmp/foo.tar.gz")).to eq("foo.tar.gz") + expect(strategy.parse_basename("file:///tmp/foo.tar.gz")).to eq("foo.tar.gz") end end end diff --git a/Library/Homebrew/test/download_strategies/curl_spec.rb b/Library/Homebrew/test/download_strategies/curl_spec.rb index 53e6dc982e..9e95ef8303 100644 --- a/Library/Homebrew/test/download_strategies/curl_spec.rb +++ b/Library/Homebrew/test/download_strategies/curl_spec.rb @@ -24,7 +24,7 @@ RSpec.describe CurlDownloadStrategy do end it "parses the opts and sets the corresponding args" do - expect(strategy.send(:_curl_args)).to eq(["--user", "download:123456"]) + expect(strategy._curl_args).to eq(["--user", "download:123456"]) end context "with a deferred HOMEBREW_ secret in a header" do @@ -38,8 +38,8 @@ RSpec.describe CurlDownloadStrategy do it "does not expand the placeholder outside Downloadable#fetch" do expect(header).to include(EnvSensitive::DEFERRED_PLACEHOLDER_PREFIX) - expect(strategy.send(:_curl_args)).to include(header) - expect(strategy.send(:_curl_args)).to include("--max-redirs", "0") + expect(strategy._curl_args).to include(header) + expect(strategy._curl_args).to include("--max-redirs", "0") end end @@ -195,7 +195,7 @@ RSpec.describe CurlDownloadStrategy do end before do - strategy.send(:allow_deferred_environment_expansion!) + strategy.allow_deferred_environment_expansion! end after { ENV.delete("HOMEBREW_PRIVATE_TOKEN") } diff --git a/Library/Homebrew/test/download_strategies/git_spec.rb b/Library/Homebrew/test/download_strategies/git_spec.rb index 99b9af01ef..5d297742df 100644 --- a/Library/Homebrew/test/download_strategies/git_spec.rb +++ b/Library/Homebrew/test/download_strategies/git_spec.rb @@ -18,7 +18,7 @@ RSpec.describe GitDownloadStrategy do describe "#clone_args" do it "terminates options before the URL" do - expect(strategy.send(:clone_args)).to end_with("--end-of-options", url, cached_location.to_s) + expect(strategy.clone_args).to end_with("--end-of-options", url, cached_location.to_s) end end @@ -32,7 +32,7 @@ RSpec.describe GitDownloadStrategy do ) .and_return(instance_double(SystemCommand::Result, success?: true)) - strategy.send(:ref?) + strategy.ref? end end diff --git a/Library/Homebrew/test/download_strategies/github_git_spec.rb b/Library/Homebrew/test/download_strategies/github_git_spec.rb index 129b25c20f..6ee8a16cc0 100644 --- a/Library/Homebrew/test/download_strategies/github_git_spec.rb +++ b/Library/Homebrew/test/download_strategies/github_git_spec.rb @@ -11,7 +11,7 @@ RSpec.describe GitHubGitDownloadStrategy do let(:version) { nil } it "parses the URL and sets the corresponding instance variables" do - expect(strategy.instance_variable_get(:@user)).to eq("homebrew") - expect(strategy.instance_variable_get(:@repo)).to eq("brew") + expect(strategy.user).to eq("homebrew") + expect(strategy.repo).to eq("brew") end end diff --git a/Library/Homebrew/test/formula_auditor_spec.rb b/Library/Homebrew/test/formula_auditor_spec.rb index 16ba12c087..f325557701 100644 --- a/Library/Homebrew/test/formula_auditor_spec.rb +++ b/Library/Homebrew/test/formula_auditor_spec.rb @@ -1605,7 +1605,7 @@ RSpec.describe Homebrew::FormulaAuditor do .and_return("merge-base-sha\n") allow(Utils).to receive(:safe_popen_read).and_return("Formula/f/foo.rb\n") - paths = auditor.send(:changed_formulae_paths, tap, only_names: ["foo"]) + paths = auditor.changed_formulae_paths(tap, only_names: ["foo"]) expect(paths).to eq([foo_path]) end @@ -1621,7 +1621,7 @@ RSpec.describe Homebrew::FormulaAuditor do "merge-base-sha") .and_return("Formula/f/foo.rb\n") - expect(auditor.send(:changed_formulae_paths, tap, only_names: ["foo"])).to eq([foo_path]) + expect(auditor.changed_formulae_paths(tap, only_names: ["foo"])).to eq([foo_path]) end end @@ -1654,7 +1654,7 @@ RSpec.describe Homebrew::FormulaAuditor do allow(FormulaVersions).to receive(:new).with(target_formula).and_return(formula_versions) expect(formula_versions).to receive(:rev_list).with("merge-base-sha") - auditor.send(:committed_version_info) + auditor.committed_version_info end end diff --git a/Library/Homebrew/test/formula_installer_spec.rb b/Library/Homebrew/test/formula_installer_spec.rb index 1b28e0771f..7a05403513 100644 --- a/Library/Homebrew/test/formula_installer_spec.rb +++ b/Library/Homebrew/test/formula_installer_spec.rb @@ -278,7 +278,7 @@ RSpec.describe FormulaInstaller do expect(tap).not_to receive(:ensure_installed!) - expect { installer.send(:verify_deps_exist) } + expect { installer.verify_deps_exist } .to raise_error(TapFormulaUnavailableError, /If you trust this tap/) { |error| expect(error.dependent).to eq(formula.full_name) } @@ -328,7 +328,7 @@ RSpec.describe FormulaInstaller do manifest_resource = formula.bottle&.github_packages_manifest_resource allow(manifest_resource).to receive(:downloaded?).and_return(true) - manifest_resource&.instance_variable_set(:@manifest_annotations, {}) + manifest_resource&.manifest_annotations = {} expect(manifest_resource).to receive(:verify_download_integrity) do expect(Context.current.quiet?).to be(true) raise Resource::BottleManifest::Error @@ -337,7 +337,10 @@ RSpec.describe FormulaInstaller do installer.fetch_bottle_tab(enqueue: true) + # Read the raw ivar: the private memoising reader would re-parse the manifest. + # rubocop:disable Homebrew/NoInstanceVariableAccessInTests expect(manifest_resource&.instance_variable_get(:@manifest_annotations)).to be_nil + # rubocop:enable Homebrew/NoInstanceVariableAccessInTests end end @@ -358,7 +361,7 @@ RSpec.describe FormulaInstaller do end it "starts a bottle download before enqueueing dependencies after the prelude" do - installer.instance_variable_set(:@ran_prelude, true) + installer.ran_prelude = true expect(download_queue).to receive(:enqueue) .with(bottle, check_attestation: false, stage: false).ordered @@ -419,7 +422,7 @@ RSpec.describe FormulaInstaller do instance end - installer.send(:install_dependency, dependency) + installer.install_dependency(dependency) expect(child_installer).not_to be_installed_on_request expect(child_installer&.link_keg).to be true @@ -825,7 +828,7 @@ RSpec.describe FormulaInstaller do installer = described_class.new(keg_only_formula, installed_on_request: true) - expect(installer.send(:link_manual_command_warning)).to eq <<~EOS + expect(installer.link_manual_command_warning).to eq <<~EOS #{formula_name} was installed but not linked because #{base_name} is already linked. To link this version, run: brew link #{formula_name} diff --git a/Library/Homebrew/test/formula_spec.rb b/Library/Homebrew/test/formula_spec.rb index 8ade69d244..ed415e8789 100644 --- a/Library/Homebrew/test/formula_spec.rb +++ b/Library/Homebrew/test/formula_spec.rb @@ -637,8 +637,8 @@ RSpec.describe Formula do specify "#migration_needed" do f = Testball.new("newname") - f.instance_variable_set(:@oldnames, ["oldname"]) - f.instance_variable_set(:@tap, CoreTap.instance) + f.oldnames = ["oldname"] + f.tap = CoreTap.instance oldname_prefix = (HOMEBREW_CELLAR/"oldname/2.20") newname_prefix = (HOMEBREW_CELLAR/"newname/2.10") @@ -953,7 +953,7 @@ RSpec.describe Formula do end expect(f.active_spec_sym).to eq(:stable) - expect(f.send(:active_spec)).to eq(f.stable) + expect(f.active_spec).to eq(f.stable) expect(f.pkg_version.to_s).to eq("1.0_1") expect { f.active_spec = :head }.to raise_error(FormulaSpecificationError) @@ -2304,7 +2304,7 @@ RSpec.describe Formula do end example "greater same tap installed" do - f.instance_variable_set(:@tap, CoreTap.instance) + f.tap = CoreTap.instance setup_tab_for_prefix(greater_prefix, tap: "homebrew/core") expect(f.outdated_kegs).to be_empty end @@ -2315,7 +2315,7 @@ RSpec.describe Formula do end example "outdated same tap installed" do - f.instance_variable_set(:@tap, CoreTap.instance) + f.tap = CoreTap.instance setup_tab_for_prefix(outdated_prefix, tap: "homebrew/core") expect(f.outdated_kegs).not_to be_empty end @@ -2401,19 +2401,19 @@ RSpec.describe Formula do end example "outdated same head installed" do - f.instance_variable_set(:@tap, CoreTap.instance) + f.tap = CoreTap.instance setup_tab_for_prefix(head_prefix, tap: "homebrew/core") expect(f.outdated_kegs).to be_empty end example "outdated different head installed" do - f.instance_variable_set(:@tap, CoreTap.instance) + f.tap = CoreTap.instance setup_tab_for_prefix(head_prefix, tap: "user/repo") expect(f.outdated_kegs).to be_empty end example "outdated mixed taps greater version installed" do - f.instance_variable_set(:@tap, CoreTap.instance) + f.tap = CoreTap.instance setup_tab_for_prefix(outdated_prefix, tap: "homebrew/core") setup_tab_for_prefix(greater_prefix, tap: "user/repo") @@ -2426,7 +2426,7 @@ RSpec.describe Formula do end example "outdated mixed taps outdated version installed" do - f.instance_variable_set(:@tap, CoreTap.instance) + f.tap = CoreTap.instance extra_outdated_prefix = HOMEBREW_CELLAR/f.name/"1.0" @@ -2443,7 +2443,7 @@ RSpec.describe Formula do end example "outdated same version tap installed" do - f.instance_variable_set(:@tap, CoreTap.instance) + f.tap = CoreTap.instance setup_tab_for_prefix(same_prefix, tap: "homebrew/core") expect(f.outdated_kegs).to be_empty @@ -2964,7 +2964,7 @@ RSpec.describe Formula do actions.each do |action| it "can #{action} network access for #{phase}" do f = Class.new(Testball) do - send(:"#{action}_network_access!", phase) + public_send(:"#{action}_network_access!", phase) end expect(f.network_access_allowed?(phase)).to be(action == "allow") @@ -2975,7 +2975,7 @@ RSpec.describe Formula do actions.each do |action| it "can #{action} network access for all phases" do f = Class.new(Testball) do - send(:"#{action}_network_access!") + public_send(:"#{action}_network_access!") end PHASES.each do |phase| @@ -3285,13 +3285,13 @@ RSpec.describe Formula do end it "sets Bundler cooldown for RubyGems dependencies" do - expect(f.send(:common_sandbox_env, mktmpdir)[:BUNDLE_COOLDOWN]).to eq("1") + expect(f.common_sandbox_env(mktmpdir)[:BUNDLE_COOLDOWN]).to eq("1") end it "prevents build tools from reading user configuration" do home = mktmpdir - expect(f.send(:common_sandbox_env, home)).to include( + expect(f.common_sandbox_env(home)).to include( GIT_CONFIG_GLOBAL: Utils::Git.no_global_config_file, GIT_TERMINAL_PROMPT: "0", GOENV: "off", diff --git a/Library/Homebrew/test/github_packages_spec.rb b/Library/Homebrew/test/github_packages_spec.rb index 153c26a124..4c60c64644 100644 --- a/Library/Homebrew/test/github_packages_spec.rb +++ b/Library/Homebrew/test/github_packages_spec.rb @@ -17,53 +17,53 @@ RSpec.describe GitHubPackages do end.new expect do - github_packages.send(:upload_bottle, "brewtest", "ghp_test", Pathname("skopeo"), "testball", - { - "formula" => { - "name" => "testball", - "pkg_version" => "1.0", - "tap_git_path" => "Formula/t/testball.rb", - "tap_git_revision" => "abcdef", - "desc" => "Test formula", - "license" => "MIT", - "homepage" => "https://brew.sh/testball", - }, - "bottle" => { - "root_url" => "https://ghcr.io/v2/homebrew/core", - "rebuild" => 0, - "date" => "2026-05-10T00:00:00Z", - "tags" => { - "all" => { - "local_filename" => bottle.to_s, - "tab" => { - "arch" => "arm64", - "built_on" => { - "os" => "Macintosh", - "os_version" => "macOS 15", - }, - }, - "sbom" => { - "documentDescribes" => ["SPDXRef-Compiler"], - "packages" => [{ "SPDXID" => "SPDXRef-Compiler" }], - "relationships" => [], - }, - "installed_size" => 100, - }, - "arm64_sonoma" => { - "local_filename" => bottle.to_s, - "tab" => { - "arch" => "arm64", - "built_on" => { - "os" => "Macintosh", - "os_version" => "macOS 14", - }, - }, - "installed_size" => 100, - }, - }, - }, - }, - keep_old: false, dry_run: true, warn_on_error: false) + github_packages.upload_bottle("brewtest", "ghp_test", Pathname("skopeo"), "testball", + { + "formula" => { + "name" => "testball", + "pkg_version" => "1.0", + "tap_git_path" => "Formula/t/testball.rb", + "tap_git_revision" => "abcdef", + "desc" => "Test formula", + "license" => "MIT", + "homepage" => "https://brew.sh/testball", + }, + "bottle" => { + "root_url" => "https://ghcr.io/v2/homebrew/core", + "rebuild" => 0, + "date" => "2026-05-10T00:00:00Z", + "tags" => { + "all" => { + "local_filename" => bottle.to_s, + "tab" => { + "arch" => "arm64", + "built_on" => { + "os" => "Macintosh", + "os_version" => "macOS 15", + }, + }, + "sbom" => { + "documentDescribes" => ["SPDXRef-Compiler"], + "packages" => [{ "SPDXID" => "SPDXRef-Compiler" }], + "relationships" => [], + }, + "installed_size" => 100, + }, + "arm64_sonoma" => { + "local_filename" => bottle.to_s, + "tab" => { + "arch" => "arm64", + "built_on" => { + "os" => "Macintosh", + "os_version" => "macOS 14", + }, + }, + "installed_size" => 100, + }, + }, + }, + }, + keep_old: false, dry_run: true, warn_on_error: false) end.to output.to_stdout index_json = JSON.parse(Pathname("testball--1.0/index.json").read) diff --git a/Library/Homebrew/test/github_runner_matrix_spec.rb b/Library/Homebrew/test/github_runner_matrix_spec.rb index db81d47b28..0516b201aa 100644 --- a/Library/Homebrew/test/github_runner_matrix_spec.rb +++ b/Library/Homebrew/test/github_runner_matrix_spec.rb @@ -68,7 +68,7 @@ RSpec.describe GitHubRunnerMatrix, :no_api do it "is idempotent" do matrix = described_class.new([], [], all_supported: false, dependent_matrix: false) runners = matrix.runners.dup - matrix.send(:generate_runners!) + matrix.generate_runners! expect(matrix.runners).to eq(runners) end @@ -378,7 +378,7 @@ RSpec.describe GitHubRunnerMatrix, :no_api do dependencies.each { |dependency| depends_on dependency } kwargs.each do |k, v| - send(:"on_#{k}") do + public_send(:"on_#{k}") do v.each do |dep| depends_on dep end diff --git a/Library/Homebrew/test/install_spec.rb b/Library/Homebrew/test/install_spec.rb index cff775f241..4b090f2835 100644 --- a/Library/Homebrew/test/install_spec.rb +++ b/Library/Homebrew/test/install_spec.rb @@ -21,7 +21,7 @@ RSpec.describe Homebrew::Install do .with(:fatal_preinstall_checks) .ordered - described_class.send(:perform_preinstall_checks) + described_class.perform_preinstall_checks end describe "::print_dry_run_dependencies" do diff --git a/Library/Homebrew/test/language/node_spec.rb b/Library/Homebrew/test/language/node_spec.rb index cad60d713b..d26c5996ea 100644 --- a/Library/Homebrew/test/language/node_spec.rb +++ b/Library/Homebrew/test/language/node_spec.rb @@ -8,7 +8,7 @@ RSpec.describe Language::Node do describe "#setup_npm_environment" do before do - described_class.instance_variable_set(:@env_set, false) + described_class.env_set = false end it "calls prepend_path when node formula exists only during the first call" do @@ -22,7 +22,7 @@ RSpec.describe Language::Node do end described_class.setup_npm_environment - expect(described_class.instance_variable_get(:@env_set)).to be(true) + expect(described_class.env_set).to be(true) without_partial_double_verification do expect(ENV).not_to receive(:prepend_path) end diff --git a/Library/Homebrew/test/language/python/virtualenv_spec.rb b/Library/Homebrew/test/language/python/virtualenv_spec.rb index cd4c79fa98..ed2304b74c 100644 --- a/Library/Homebrew/test/language/python/virtualenv_spec.rb +++ b/Library/Homebrew/test/language/python/virtualenv_spec.rb @@ -42,7 +42,7 @@ RSpec.describe Language::Python::Virtualenv, :needs_python do let(:r_d) { f.resource("resource-d") } let(:buildpath) { Pathname(TEST_TMPDIR) } - before { f.instance_variable_set(:@buildpath, buildpath) } + before { f.buildpath = buildpath } it "works with `using: \"python\"` and installs resources in order" do expect(f).to receive(:virtualenv_create).with( diff --git a/Library/Homebrew/test/livecheck/livecheck_spec.rb b/Library/Homebrew/test/livecheck/livecheck_spec.rb index 82d3c478a7..158fed9786 100644 --- a/Library/Homebrew/test/livecheck/livecheck_spec.rb +++ b/Library/Homebrew/test/livecheck/livecheck_spec.rb @@ -78,8 +78,8 @@ RSpec.describe Homebrew::Livecheck do context "when provided with a strategy class" do it "returns demodulized class name" do # We run this twice with the same argument to exercise the caching logic - expect(livecheck.send(:livecheck_strategy_names, Homebrew::Livecheck::Strategy::PageMatch)).to eq("PageMatch") - expect(livecheck.send(:livecheck_strategy_names, Homebrew::Livecheck::Strategy::PageMatch)).to eq("PageMatch") + expect(livecheck.livecheck_strategy_names(Homebrew::Livecheck::Strategy::PageMatch)).to eq("PageMatch") + expect(livecheck.livecheck_strategy_names(Homebrew::Livecheck::Strategy::PageMatch)).to eq("PageMatch") end end end @@ -96,8 +96,8 @@ RSpec.describe Homebrew::Livecheck do parameters = strategy_class.method(:find_versions).parameters.map(&:second) # We run this twice with the same argument to exercise the caching logic - expect(livecheck.send(:livecheck_find_versions_parameters, strategy_class)).to eq(parameters) - expect(livecheck.send(:livecheck_find_versions_parameters, strategy_class)).to eq(parameters) + expect(livecheck.livecheck_find_versions_parameters(strategy_class)).to eq(parameters) + expect(livecheck.livecheck_find_versions_parameters(strategy_class)).to eq(parameters) end end end @@ -522,61 +522,61 @@ RSpec.describe Homebrew::Livecheck do let(:version) { Version.new("1.2.3") } it "returns true if `throttle_rate` and `throttle_days` are nil" do - expect(livecheck.send(:throttle_allows_bump?, f, version)).to be(true) + expect(livecheck.throttle_allows_bump?(f, version)).to be(true) end it "returns true if patch version is divisible by `throttle_rate`" do - expect(livecheck.send(:throttle_allows_bump?, f, version, throttle_rate: 3)).to be(true) - expect(livecheck.send(:throttle_allows_bump?, f, "1.2.3", throttle_rate: 3)).to be(true) + expect(livecheck.throttle_allows_bump?(f, version, throttle_rate: 3)).to be(true) + expect(livecheck.throttle_allows_bump?(f, "1.2.3", throttle_rate: 3)).to be(true) end it "returns false if patch version is not divisible by `throttle_rate` and `throttle_days` is not set" do - expect(livecheck.send(:throttle_allows_bump?, f, version, throttle_rate: 5)).to be(false) + expect(livecheck.throttle_allows_bump?(f, version, throttle_rate: 5)).to be(false) end it "returns false if patch version is not divisible by `throttle_rate` and throttle interval has not elapsed" do allow(livecheck).to receive(:throttle_interval_elapsed?).and_return(false) - expect(livecheck.send(:throttle_allows_bump?, f, version, throttle_rate: 5, throttle_days: 1)).to be(false) + expect(livecheck.throttle_allows_bump?(f, version, throttle_rate: 5, throttle_days: 1)).to be(false) end it "returns true if patch version is not divisible by `throttle_rate` and throttle interval has elapsed" do allow(livecheck).to receive(:throttle_interval_elapsed?).and_return(true) - expect(livecheck.send(:throttle_allows_bump?, f, version, throttle_rate: 5, throttle_days: 1)).to be(true) + expect(livecheck.throttle_allows_bump?(f, version, throttle_rate: 5, throttle_days: 1)).to be(true) end it "returns false if only `throttle_days` is provided and throttle interval has not elapsed" do allow(livecheck).to receive(:throttle_interval_elapsed?).and_return(false) - expect(livecheck.send(:throttle_allows_bump?, f, version, throttle_days: 1)).to be(false) + expect(livecheck.throttle_allows_bump?(f, version, throttle_days: 1)).to be(false) end it "returns true if only `throttle_days` is provided and throttle interval has elapsed" do allow(livecheck).to receive(:throttle_interval_elapsed?).and_return(true) - expect(livecheck.send(:throttle_allows_bump?, f, version, throttle_days: 1)).to be(true) + expect(livecheck.throttle_allows_bump?(f, version, throttle_days: 1)).to be(true) end end describe "::throttle_interval_elapsed" do it "returns false if days is not positive" do - expect(livecheck.send(:throttle_interval_elapsed?, f, 0)).to be(false) - expect(livecheck.send(:throttle_interval_elapsed?, f, -1)).to be(false) + expect(livecheck.throttle_interval_elapsed?(f, 0)).to be(false) + expect(livecheck.throttle_interval_elapsed?(f, -1)).to be(false) end it "returns false if last_updated_timestamp can't be determined" do allow(livecheck).to receive(:formula_or_cask_last_updated_timestamp).and_return(nil) - expect(livecheck.send(:throttle_interval_elapsed?, f, 4)).to be(false) + expect(livecheck.throttle_interval_elapsed?(f, 4)).to be(false) end it "returns false if throttle interval has not elapsed" do allow(livecheck).to receive(:formula_or_cask_last_updated_timestamp).and_return(Time.now.to_i) - expect(livecheck.send(:throttle_interval_elapsed?, f, 4)).to be(false) + expect(livecheck.throttle_interval_elapsed?(f, 4)).to be(false) end it "returns true if throttle interval has elapsed" do allow(livecheck).to receive(:formula_or_cask_last_updated_timestamp).and_return(Time.now.to_i - 518400) - expect(livecheck.send(:throttle_interval_elapsed?, f, 4)).to be(true) + expect(livecheck.throttle_interval_elapsed?(f, 4)).to be(true) end end @@ -623,7 +623,7 @@ RSpec.describe Homebrew::Livecheck do .with(Utils::Git.git, "show", "-s", "--format=%ct", "bbb222", chdir: tap_path) .and_return("1711731600\n") - expect(livecheck.send(:formula_or_cask_last_updated_timestamp, f)).to eq(1711731600) + expect(livecheck.formula_or_cask_last_updated_timestamp(f)).to eq(1711731600) end it "falls back to latest file commit timestamp for casks" do @@ -634,7 +634,7 @@ RSpec.describe Homebrew::Livecheck do allow(Utils::Git).to receive(:available?).and_return(true) allow(Utils).to receive(:popen_read).and_return("1711731600\n") - expect(livecheck.send(:formula_or_cask_last_updated_timestamp, c)).to eq(1711731600) + expect(livecheck.formula_or_cask_last_updated_timestamp(c)).to eq(1711731600) end end end diff --git a/Library/Homebrew/test/livecheck/strategy/git_spec.rb b/Library/Homebrew/test/livecheck/strategy/git_spec.rb index 795b3cd813..6c28b75e9c 100644 --- a/Library/Homebrew/test/livecheck/strategy/git_spec.rb +++ b/Library/Homebrew/test/livecheck/strategy/git_spec.rb @@ -55,7 +55,7 @@ RSpec.describe Homebrew::Livecheck::Strategy::Git do before do # Clear the processed URL cache before each test, to ensure that we're # properly testing the method's processing logic. - git.instance_variable_set(:@processed_urls, {}) + git.processed_urls = {} end let(:github_git_url_with_extension) { "https://github.com/Homebrew/brew.git" } @@ -64,7 +64,7 @@ RSpec.describe Homebrew::Livecheck::Strategy::Git do # This uses an unrealistic value to make sure that we are receiving a # cached value from `@processed_urls` and not a newly-processed URL. cached_value = "CACHED" - git.instance_variable_set(:@processed_urls, { non_git_url => cached_value }) + git.processed_urls = { non_git_url => cached_value } expect(git.preprocess_url(non_git_url)).to eq(cached_value) end diff --git a/Library/Homebrew/test/livecheck/strategy/xorg_spec.rb b/Library/Homebrew/test/livecheck/strategy/xorg_spec.rb index da2478a3fa..50eb7e3123 100644 --- a/Library/Homebrew/test/livecheck/strategy/xorg_spec.rb +++ b/Library/Homebrew/test/livecheck/strategy/xorg_spec.rb @@ -163,7 +163,7 @@ RSpec.describe Homebrew::Livecheck::Strategy::Xorg do } end - before { xorg.instance_variable_set(:@page_data, {}) } + before { xorg.page_data = {} } it "finds versions in fetched content" do allow(Homebrew::Livecheck::Strategy).to receive(:page_content).and_return({ content: }) @@ -172,10 +172,7 @@ RSpec.describe Homebrew::Livecheck::Strategy::Xorg do end it "finds versions in cached content" do - xorg.instance_variable_set( - :@page_data, - { generated[:app][:url] => content }, - ) + xorg.page_data = { generated[:app][:url] => content } expect(xorg.find_versions(url: xorg_urls[:app])).to eq(match_data[:cached]) end diff --git a/Library/Homebrew/test/livecheck_spec.rb b/Library/Homebrew/test/livecheck_spec.rb index e76cab5a80..ff14515c1c 100644 --- a/Library/Homebrew/test/livecheck_spec.rb +++ b/Library/Homebrew/test/livecheck_spec.rb @@ -74,14 +74,14 @@ RSpec.describe Livecheck do describe "#skip" do it "sets @skip to true when no argument is provided" do expect(livecheck_f.skip).to be true - expect(livecheck_f.instance_variable_get(:@skip)).to be true - expect(livecheck_f.instance_variable_get(:@skip_msg)).to be_nil + expect(livecheck_f.skip?).to be true + expect(livecheck_f.skip_msg).to be_nil end it "sets @skip to true and @skip_msg to the provided String" do expect(livecheck_f.skip("foo")).to be true - expect(livecheck_f.instance_variable_get(:@skip)).to be true - expect(livecheck_f.instance_variable_get(:@skip_msg)).to eq("foo") + expect(livecheck_f.skip?).to be true + expect(livecheck_f.skip_msg).to eq("foo") end end diff --git a/Library/Homebrew/test/macos_version_spec.rb b/Library/Homebrew/test/macos_version_spec.rb index f5c06afe2c..90704c08a8 100644 --- a/Library/Homebrew/test/macos_version_spec.rb +++ b/Library/Homebrew/test/macos_version_spec.rb @@ -50,7 +50,7 @@ RSpec.describe MacOSVersion do end end - specify "comparison with Symbol" do + specify "comparisons" do expect(version).to be >= :catalina expect(version).to eq :catalina # We're explicitly testing the `===` operator results here. @@ -60,10 +60,8 @@ RSpec.describe MacOSVersion do # This should work like a normal comparison but the result won't be added # to the `@comparison_cache` hash because the object is frozen. expect(frozen_version).to eq :catalina - expect(frozen_version.instance_variable_get(:@comparison_cache)).to eq({}) - end + expect(frozen_version.comparison_cache).to eq({}) - specify do expect(version).to be > 10 expect(version).to be < 11 expect(version).to be > "10.3" @@ -120,7 +118,7 @@ RSpec.describe MacOSVersion do # This should work like a normal but the symbol won't be stored as the # `@sym` instance variable because the object is frozen. expect(frozen_version.to_sym).to eq(version_symbol) - expect(frozen_version.instance_variable_get(:@sym)).to be_nil + expect(frozen_version.sym).to be_nil expect(MacOSVersion::NULL.to_sym).to eq(:dunno) end @@ -137,7 +135,10 @@ RSpec.describe MacOSVersion do # This should work like a normal but the computed name won't be stored as # the `@pretty_name` instance variable because the object is frozen. expect(frozen_version.pretty_name).to eq(version_pretty_name) + # Read the raw ivar: a reader would collide with the memoising `pretty_name`. + # rubocop:disable Homebrew/NoInstanceVariableAccessInTests expect(frozen_version.instance_variable_get(:@pretty_name)).to be_nil + # rubocop:enable Homebrew/NoInstanceVariableAccessInTests end describe "#requires_nehalem_cpu?", :needs_macos do diff --git a/Library/Homebrew/test/mcp_server_spec.rb b/Library/Homebrew/test/mcp_server_spec.rb index 8158fba93b..fb19e8b2d1 100644 --- a/Library/Homebrew/test/mcp_server_spec.rb +++ b/Library/Homebrew/test/mcp_server_spec.rb @@ -203,7 +203,7 @@ RSpec.describe Homebrew::McpServer do describe "#respond_result" do it "returns nil if id is nil" do - expect(server.send(:respond_result, nil, {})).to be_nil + expect(server.respond_result(nil, {})).to be_nil end it "returns a result hash if id is present" do diff --git a/Library/Homebrew/test/os/linux/dependency_collector_spec.rb b/Library/Homebrew/test/os/linux/dependency_collector_spec.rb index 0aa7f159ac..55000447c2 100644 --- a/Library/Homebrew/test/os/linux/dependency_collector_spec.rb +++ b/Library/Homebrew/test/os/linux/dependency_collector_spec.rb @@ -96,7 +96,7 @@ RSpec.describe DependencyCollector do end it "returns nil for Bubblewrap and its dependencies" do - collector.send(:global_dep_tree)["bubblewrap"] = Set["libcap"] + collector.global_dep_tree["bubblewrap"] = Set["libcap"] expect(collector.bubblewrap_dep_if_needed(Set["bubblewrap"])).to be_nil expect(collector.bubblewrap_dep_if_needed(Set["libcap"])).to be_nil diff --git a/Library/Homebrew/test/os/linux/ld_spec.rb b/Library/Homebrew/test/os/linux/ld_spec.rb index f3a8c90d53..b82c6670e4 100644 --- a/Library/Homebrew/test/os/linux/ld_spec.rb +++ b/Library/Homebrew/test/os/linux/ld_spec.rb @@ -19,7 +19,7 @@ RSpec.describe OS::Linux::Ld do before do allow(File).to receive(:executable?).and_return(false) - described_class.instance_variable_set(:@system_ld_so, nil) + described_class.system_ld_so = nil end it "returns the path to a known dynamic linker" do diff --git a/Library/Homebrew/test/os/linux/libstdcxx_spec.rb b/Library/Homebrew/test/os/linux/libstdcxx_spec.rb index 5209167b7d..94090c35a9 100644 --- a/Library/Homebrew/test/os/linux/libstdcxx_spec.rb +++ b/Library/Homebrew/test/os/linux/libstdcxx_spec.rb @@ -23,7 +23,7 @@ RSpec.describe OS::Linux::Libstdcxx do before do tmpdir.mkpath - described_class.instance_variable_set(:@system_version, nil) + described_class.system_version = nil allow(described_class).to receive(:system_path).and_return(libstdcxx) end diff --git a/Library/Homebrew/test/pathname_spec.rb b/Library/Homebrew/test/pathname_spec.rb index 742881e5e0..f760dad2f7 100644 --- a/Library/Homebrew/test/pathname_spec.rb +++ b/Library/Homebrew/test/pathname_spec.rb @@ -17,7 +17,10 @@ RSpec.describe Pathname do pathname = Pathname.new(file.to_s) [:@magic_number, :@file_type, :@zipinfo, :@which_install_info, :@disk_usage, :@file_count].each do |ivar| expect(pathname.instance_variable_defined?(ivar)).to be(true), "expected #{ivar} to be defined" + # Read the raw ivars: the names are dynamic and eager raw definition is under test. + # rubocop:disable Homebrew/NoInstanceVariableAccessInTests expect(pathname.instance_variable_get(ivar)).to be_nil + # rubocop:enable Homebrew/NoInstanceVariableAccessInTests end end end diff --git a/Library/Homebrew/test/reinstall_spec.rb b/Library/Homebrew/test/reinstall_spec.rb index 704120c52f..d7ca2fcbb1 100644 --- a/Library/Homebrew/test/reinstall_spec.rb +++ b/Library/Homebrew/test/reinstall_spec.rb @@ -71,7 +71,7 @@ RSpec.describe Homebrew::Reinstall do (backup/"bin").mkpath (backup/"bin/test").write("stale") - described_class.send(:backup, keg) + described_class.backup(keg) expect(keg_path).not_to exist expect(backup/"bin/test").to exist diff --git a/Library/Homebrew/test/rubocops/no_instance_variable_access_in_tests_spec.rb b/Library/Homebrew/test/rubocops/no_instance_variable_access_in_tests_spec.rb new file mode 100644 index 0000000000..29bc7fe36d --- /dev/null +++ b/Library/Homebrew/test/rubocops/no_instance_variable_access_in_tests_spec.rb @@ -0,0 +1,46 @@ +# typed: strict +# frozen_string_literal: true + +require "rubocops/no_instance_variable_access_in_tests" + +RSpec.describe RuboCop::Cop::Homebrew::NoInstanceVariableAccessInTests, :config do + it "registers an offense when using `instance_variable_get`" do + expect_offense(<<~RUBY) + formula.instance_variable_get(:@tap) + ^^^^^^^^^^^^^^^^^^^^^ Use a public `attr_reader`/`attr_writer` (or an existing accessor) instead of `instance_variable_get` in tests. + RUBY + end + + it "registers an offense when using `instance_variable_set`" do + expect_offense(<<~RUBY) + formula.instance_variable_set(:@tap, CoreTap.instance) + ^^^^^^^^^^^^^^^^^^^^^ Use a public `attr_reader`/`attr_writer` (or an existing accessor) instead of `instance_variable_set` in tests. + RUBY + end + + it "registers an offense when using `instance_variable_set` without a receiver" do + expect_offense(<<~RUBY) + instance_variable_set(:@staged_path, tmp_staged) + ^^^^^^^^^^^^^^^^^^^^^ Use a public `attr_reader`/`attr_writer` (or an existing accessor) instead of `instance_variable_set` in tests. + RUBY + end + + it "registers an offense when using `instance_variable_get` with a dynamic name" do + expect_offense(<<~RUBY) + pathname.instance_variable_get(ivar) + ^^^^^^^^^^^^^^^^^^^^^ Use a public `attr_reader`/`attr_writer` (or an existing accessor) instead of `instance_variable_get` in tests. + RUBY + end + + it "does not register an offense when using `instance_variable_defined?`" do + expect_no_offenses(<<~RUBY) + described_class.instance_variable_defined?(:@version) + RUBY + end + + it "does not register an offense for direct accessor calls" do + expect_no_offenses(<<~RUBY) + formula.tap = CoreTap.instance + RUBY + end +end diff --git a/Library/Homebrew/test/rubocops/no_send_in_tests_spec.rb b/Library/Homebrew/test/rubocops/no_send_in_tests_spec.rb new file mode 100644 index 0000000000..be7bed2a3a --- /dev/null +++ b/Library/Homebrew/test/rubocops/no_send_in_tests_spec.rb @@ -0,0 +1,86 @@ +# typed: strict +# frozen_string_literal: true + +require "rubocops/no_send_in_tests" + +RSpec.describe RuboCop::Cop::Homebrew::NoSendInTests, :config do + it "registers an offense when using `send` with a static method name" do + expect_offense(<<~RUBY) + formula.send(:active_spec) + ^^^^ Make the method public and call it directly instead of using `send` in tests. + RUBY + end + + it "registers an offense when using `send` without a receiver" do + expect_offense(<<~RUBY) + send(:generate_runners!) + ^^^^ Make the method public and call it directly instead of using `send` in tests. + RUBY + end + + it "registers an offense when using `__send__`" do + expect_offense(<<~RUBY) + formula.__send__(:active_spec) + ^^^^^^^^ Make the method public and call it directly instead of using `__send__` in tests. + RUBY + end + + it "registers an offense when using `send` with a safe navigation operator" do + expect_offense(<<~RUBY) + formula&.send(:active_spec) + ^^^^ Make the method public and call it directly instead of using `send` in tests. + RUBY + end + + it "registers an offense when using `send` with a dynamic method name" do + expect_offense(<<~'RUBY') + formula.send(:"#{action}_network_access!") + ^^^^ Use `public_send` instead of `send` in tests; `send` bypasses method visibility. + RUBY + end + + it "registers an offense when using `public_send` with a static method name" do + expect_offense(<<~RUBY) + formula.public_send(:active_spec) + ^^^^^^^^^^^ Call the method directly instead of using `public_send` with a static method name. + RUBY + end + + it "registers an offense when using `public_send` with a static string method name" do + expect_offense(<<~RUBY) + formula.public_send("active_spec") + ^^^^^^^^^^^ Call the method directly instead of using `public_send` with a static method name. + RUBY + end + + it "registers an offense when using `public_send` with a static setter method name" do + expect_offense(<<~RUBY) + formula.public_send(:name=, "foo") + ^^^^^^^^^^^ Call the method directly instead of using `public_send` with a static method name. + RUBY + end + + it "does not register an offense when using `public_send` with a dynamic method name" do + expect_no_offenses(<<~'RUBY') + subject.public_send(:"#{artifact_dsl_key}_phase", command: fake_system_command) + RUBY + end + + it "does not register an offense when using `public_send` with a variable method name" do + expect_no_offenses(<<~RUBY) + described_class.public_send(method_name, TEST_TMPDIR, safe: false) + RUBY + end + + it "does not register an offense when using `public_send` with a method name that has no call syntax" do + expect_no_offenses(<<~RUBY) + subject.public_send(:"gcc-9") + RUBY + end + + it "does not register an offense for a direct method call" do + expect_no_offenses(<<~RUBY) + formula.active_spec + RUBY + end +end diff --git a/Library/Homebrew/test/sandbox_landlock_spec.rb b/Library/Homebrew/test/sandbox_landlock_spec.rb index 795d0aa6f0..9dd0195e9a 100644 --- a/Library/Homebrew/test/sandbox_landlock_spec.rb +++ b/Library/Homebrew/test/sandbox_landlock_spec.rb @@ -5,7 +5,7 @@ require "sandbox" require "extend/os/linux/sandbox/landlock" RSpec.describe Sandbox::Landlock do - subject(:landlock) { described_class.new(sandbox.send(:profile)) } + subject(:landlock) { described_class.new(sandbox.profile) } let(:sandbox) { Sandbox.new } @@ -273,7 +273,7 @@ RSpec.describe Sandbox::Landlock do sandbox.deny_all_network landlock.command(["true"], tmpdir.to_s) - attributes, = landlock.send(:ruleset_attributes, 7) + attributes, = landlock.ruleset_attributes(7) expect(attributes.unpack("Q3")).to eq([65_522, 3, 1]) end @@ -308,7 +308,7 @@ RSpec.describe Sandbox::Landlock do denied_dir.mkpath allow(landlock).to receive(:root_path).and_return(root) - expect(landlock.send(:readable_paths, [denied_dir])).to eq([readable_dir.to_s]) + expect(landlock.readable_paths([denied_dir])).to eq([readable_dir.to_s]) end it "does not allow a symlink alias into a denied hierarchy" do @@ -319,7 +319,7 @@ RSpec.describe Sandbox::Landlock do alias_path.make_symlink(denied_dir) allow(landlock).to receive(:root_path).and_return(root) - expect(landlock.send(:readable_paths, [denied_dir])).to be_empty + expect(landlock.readable_paths([denied_dir])).to be_empty end it "skips dangling symlinks" do @@ -330,7 +330,7 @@ RSpec.describe Sandbox::Landlock do dangling_path.make_symlink(root/"missing") allow(landlock).to receive(:root_path).and_return(root) - expect(landlock.send(:readable_paths, [denied_dir])).to be_empty + expect(landlock.readable_paths([denied_dir])).to be_empty end end end diff --git a/Library/Homebrew/test/sandbox_linux_spec.rb b/Library/Homebrew/test/sandbox_linux_spec.rb index feb22193d5..d976bd58fb 100644 --- a/Library/Homebrew/test/sandbox_linux_spec.rb +++ b/Library/Homebrew/test/sandbox_linux_spec.rb @@ -388,7 +388,7 @@ RSpec.describe Sandbox, :needs_linux do let(:dir) { mktmpdir } let(:denied_dir) { mktmpdir } let(:tmpdir) { mktmpdir } - let(:args) { sandbox.send(:bubblewrap_args, tmpdir.to_s) } + let(:args) { sandbox.bubblewrap_args(tmpdir.to_s) } it "maps allowed and denied writes to bind mounts" do sandbox.allow_write_path dir @@ -441,7 +441,7 @@ RSpec.describe Sandbox, :needs_linux do it "does not add Xcode write paths" do sandbox.allow_write_xcode - expect(sandbox.send(:writable_paths)).to be_empty + expect(sandbox.writable_paths).to be_empty end it "rejects regex path filters" do diff --git a/Library/Homebrew/test/sandbox_shared_spec.rb b/Library/Homebrew/test/sandbox_shared_spec.rb index 05d8ceb76c..2e0f98a551 100644 --- a/Library/Homebrew/test/sandbox_shared_spec.rb +++ b/Library/Homebrew/test/sandbox_shared_spec.rb @@ -80,7 +80,7 @@ RSpec.describe Sandbox do controller = instance_double(IO) allow(controller).to receive(:each_char).and_raise(Errno::EIO) - expect { sandbox.send(:copy_pty_output, controller) }.not_to raise_error + expect { sandbox.copy_pty_output(controller) }.not_to raise_error end end @@ -200,7 +200,7 @@ RSpec.describe Sandbox do sandbox.allow_read_if_exists path: file - rule = sandbox.send(:profile).rules.fetch(-1) + rule = sandbox.profile.rules.fetch(-1) expect(rule).to have_attributes(allow: true, operation: "file-read*") expect(rule.filter).to have_attributes(path: file.realpath.to_s, type: :literal) end @@ -208,13 +208,13 @@ RSpec.describe Sandbox do it "skips missing paths" do sandbox.allow_read_if_exists path: mktmpdir/"missing.rb" - expect(sandbox.send(:profile).rules).to be_empty + expect(sandbox.profile.rules).to be_empty end it "skips nil paths" do sandbox.allow_read_if_exists path: nil - expect(sandbox.send(:profile).rules).to be_empty + expect(sandbox.profile.rules).to be_empty end end @@ -225,7 +225,7 @@ RSpec.describe Sandbox do sandbox.deny_read_path dir - rule = sandbox.send(:profile).rules.fetch(-1) + rule = sandbox.profile.rules.fetch(-1) expect(rule).to have_attributes(allow: false, operation: "file-read*") expect(rule.filter).to have_attributes(path: dir.realpath.to_s, type: :subpath) end @@ -252,7 +252,7 @@ RSpec.describe Sandbox do it "denies reads from the real home" do sandbox.deny_read_home - rule = sandbox.send(:profile).rules.fetch(-1) + rule = sandbox.profile.rules.fetch(-1) expect(rule).to have_attributes(allow: false, operation: "file-read*") expect(rule.filter).to have_attributes(path: home.realpath.to_s, type: :subpath) end @@ -273,7 +273,7 @@ RSpec.describe Sandbox do sandbox.deny_read_home - expect(sandbox.send(:profile).rules).to be_empty + expect(sandbox.profile.rules).to be_empty end end @@ -289,7 +289,7 @@ RSpec.describe Sandbox do sandbox.deny_read_home end - expect(sandbox.send(:profile).rules).to be_empty + expect(sandbox.profile.rules).to be_empty end end @@ -302,7 +302,7 @@ RSpec.describe Sandbox do sandbox.deny_read_home end - expect(sandbox.send(:profile).rules).to be_empty + expect(sandbox.profile.rules).to be_empty end it "denies known sensitive home paths when Homebrew needs home access" do @@ -367,7 +367,7 @@ RSpec.describe Sandbox do sandbox.deny_read_home - denied = sandbox.send(:profile).rules.map { |rule| rule.filter&.path } + denied = sandbox.profile.rules.map { |rule| rule.filter&.path } expect(denied).to include(*(sensitive_dirs + sensitive_files).map { |path| path.realpath.to_s }) expect(denied).not_to include(*allowed_dirs.map { |path| path.realpath.to_s }) end @@ -378,7 +378,7 @@ RSpec.describe Sandbox do sandbox.deny_read_home - denied = sandbox.send(:profile).rules.map { |rule| rule.filter&.path } + denied = sandbox.profile.rules.map { |rule| rule.filter&.path } expect(denied).to contain_exactly((home/".ssh").realpath.to_s) end @@ -405,7 +405,7 @@ RSpec.describe Sandbox do sandbox.deny_read_home - denied = sandbox.send(:profile).rules.map { |rule| rule.filter&.path } + denied = sandbox.profile.rules.map { |rule| rule.filter&.path } expect(denied).to include((home/".ssh").realpath.to_s) expect(denied).not_to include(teams_log.realpath.to_s, backslash_dir.realpath.to_s) end @@ -417,7 +417,7 @@ RSpec.describe Sandbox do sandbox.deny_read_home - denied = sandbox.send(:profile).rules.map { |rule| rule.filter&.path } + denied = sandbox.profile.rules.map { |rule| rule.filter&.path } expect(denied).not_to include(File::NULL) end @@ -452,7 +452,7 @@ RSpec.describe Sandbox do sandbox.deny_read_home end - denied = sandbox.send(:profile).rules.map { |rule| rule.filter&.path } + denied = sandbox.profile.rules.map { |rule| rule.filter&.path } expect(denied).to include((home/".ssh").realpath.to_s) expect(denied).not_to include(trust_file.realpath.to_s) end @@ -469,7 +469,7 @@ RSpec.describe Sandbox do sandbox.deny_read_home end - denied = sandbox.send(:profile).rules.map { |rule| rule.filter&.path } + denied = sandbox.profile.rules.map { |rule| rule.filter&.path } expect(denied).to include(gh_config.realpath.to_s) expect(denied).to include((home/".ssh").realpath.to_s) expect(denied).not_to include((home/".config").realpath.to_s) @@ -483,7 +483,7 @@ RSpec.describe Sandbox do sandbox.deny_read_home - denied = sandbox.send(:profile).rules.map { |rule| rule.filter&.path } + denied = sandbox.profile.rules.map { |rule| rule.filter&.path } expect(denied).not_to include(developer.realpath.to_s, swiftpm.realpath.to_s) expect(denied).to include((home/".ssh").realpath.to_s) end @@ -496,7 +496,7 @@ RSpec.describe Sandbox do sandbox.allow_write_path_if_exists dir - rule = sandbox.send(:profile).rules.fetch(0) + rule = sandbox.profile.rules.fetch(0) expect(rule).to have_attributes(allow: true, operation: "file-write*") expect(rule.filter).to have_attributes(path: dir.realpath.to_s, type: :subpath) end @@ -504,13 +504,13 @@ RSpec.describe Sandbox do it "skips missing paths" do sandbox.allow_write_path_if_exists mktmpdir/"missing" - expect(sandbox.send(:profile).rules).to be_empty + expect(sandbox.profile.rules).to be_empty end it "skips nil paths" do sandbox.allow_write_path_if_exists nil - expect(sandbox.send(:profile).rules).to be_empty + expect(sandbox.profile.rules).to be_empty end end end diff --git a/Library/Homebrew/test/support/helper/api_hashable.rb b/Library/Homebrew/test/support/helper/api_hashable.rb index 96e7481601..520364cd0e 100644 --- a/Library/Homebrew/test/support/helper/api_hashable.rb +++ b/Library/Homebrew/test/support/helper/api_hashable.rb @@ -10,7 +10,8 @@ module APIHashable def generated_hash! return unless generating_hash? - Object.send(:remove_const, :HOMEBREW_PREFIX) + # `Module#remove_const` is private Ruby core API with no public alternative. + Object.send(:remove_const, :HOMEBREW_PREFIX) # rubocop:disable Homebrew/NoSendInTests Object.const_set(:HOMEBREW_PREFIX, @old_homebrew_prefix) ENV["HOME"] = @old_home ENV["GIT_CONFIG_GLOBAL"] = @old_git_config_global diff --git a/Library/Homebrew/test/support/helper/spec/shared_context/integration_test.rb b/Library/Homebrew/test/support/helper/spec/shared_context/integration_test.rb index dbe0fdbd63..f59f37c888 100644 --- a/Library/Homebrew/test/support/helper/spec/shared_context/integration_test.rb +++ b/Library/Homebrew/test/support/helper/spec/shared_context/integration_test.rb @@ -221,7 +221,7 @@ module Test tab = Tab.for_name(name) tab.tabfile ||= keg/AbstractTab::FILENAME tab_attributes.each do |key, value| - tab.instance_variable_set(:"@#{key}", value) + tab.public_send(:"#{key}=", value) end tab.write diff --git a/Library/Homebrew/test/support/helper/subcommand_spec.rb b/Library/Homebrew/test/support/helper/subcommand_spec.rb index 4d85f4c9c4..cccbde85d8 100644 --- a/Library/Homebrew/test/support/helper/subcommand_spec.rb +++ b/Library/Homebrew/test/support/helper/subcommand_spec.rb @@ -1,11 +1,10 @@ -# typed: strict +# typed: false # frozen_string_literal: true RSpec.describe Test::Helper::Subcommand::Args do specify "unknown predicates raise" do - unknown_predicate = :formuale? expect do - described_class.new(named: []).public_send(unknown_predicate) + described_class.new(named: []).formuale? end.to raise_error(NoMethodError) end end diff --git a/Library/Homebrew/test/tab_spec.rb b/Library/Homebrew/test/tab_spec.rb index 323b18e785..01f0fcc027 100644 --- a/Library/Homebrew/test/tab_spec.rb +++ b/Library/Homebrew/test/tab_spec.rb @@ -452,7 +452,7 @@ RSpec.describe Tab do url "file://#{repo}", using: :git, branch: "master" version "1.0" end - f.public_send(f.active_spec_sym).fetch + f.active_spec.fetch tab = described_class.create(f, DevelopmentTools.default_compiler, :libcxx) diff --git a/Library/Homebrew/test/tap_spec.rb b/Library/Homebrew/test/tap_spec.rb index 74db14d9f4..a44a17ba2b 100644 --- a/Library/Homebrew/test/tap_spec.rb +++ b/Library/Homebrew/test/tap_spec.rb @@ -432,7 +432,7 @@ RSpec.describe Tap do describe "#prefix_to_versioned_formulae_names" do it "groups versioned full formulae with their matching full formula" do - homebrew_foo_tap.instance_variable_set(:@prefix_to_versioned_formulae_names, nil) + homebrew_foo_tap.clear_cache allow(homebrew_foo_tap).to receive(:formula_names).and_return(["foo@2.0", "foo-full", "foo@2.0-full"]) expect(homebrew_foo_tap.prefix_to_versioned_formulae_names) @@ -733,7 +733,7 @@ RSpec.describe Tap do expect(SystemCommand).to receive(:run!) .with("git", args: %w[fetch], chdir: path, env: { "GIT_TERMINAL_PROMPT" => "0" }, print_stderr: true) - homebrew_foo_tap.send(:git_command!, %w[fetch], chdir: path) + homebrew_foo_tap.git_command!(%w[fetch], chdir: path) end it "raises an error when the Tap is already tapped" do diff --git a/Library/Homebrew/test/test_bot/formulae_dependents_spec.rb b/Library/Homebrew/test/test_bot/formulae_dependents_spec.rb index 53ebea5003..8fe436f34d 100644 --- a/Library/Homebrew/test/test_bot/formulae_dependents_spec.rb +++ b/Library/Homebrew/test/test_bot/formulae_dependents_spec.rb @@ -28,8 +28,7 @@ RSpec.describe Homebrew::TestBot::FormulaeDependents do stub_formula_loader dependent stub_formula_loader independent - shard = formulae_dependents.send( - :dependents_for_shard, + shard = formulae_dependents.dependents_for_shard( [ [dependency, dependency.deps.to_a], [dependent, dependent.deps.to_a], @@ -42,7 +41,7 @@ RSpec.describe Homebrew::TestBot::FormulaeDependents do end it "rejects invalid shard indexes" do - expect { formulae_dependents.send(:dependents_for_shard, [], "2/1") } + expect { formulae_dependents.dependents_for_shard([], "2/1") } .to raise_error(UsageError, /must not be greater/) end @@ -52,7 +51,7 @@ RSpec.describe Homebrew::TestBot::FormulaeDependents do url "https://brew.sh/dependent-a-1.0.tar.gz" end - expect(formulae_dependents.send(:dependents_for_shard, [[dependent, dependent.deps.to_a]], "2/2")).to be_empty + expect(formulae_dependents.dependents_for_shard([[dependent, dependent.deps.to_a]], "2/2")).to be_empty end end end diff --git a/Library/Homebrew/test/test_bot/formulae_spec.rb b/Library/Homebrew/test/test_bot/formulae_spec.rb index acfd337cc5..0f9842a6e1 100644 --- a/Library/Homebrew/test/test_bot/formulae_spec.rb +++ b/Library/Homebrew/test/test_bot/formulae_spec.rb @@ -17,11 +17,11 @@ RSpec.describe Homebrew::TestBot::Formulae do output_paths: ) - expect(formulae.send(:dependency_name_match?, Dependency.new("foo"), "foo")).to be(true) - expect(formulae.send(:dependency_name_match?, Dependency.new("homebrew/core/foo"), "homebrew/core/foo")) + expect(formulae.dependency_name_match?(Dependency.new("foo"), "foo")).to be(true) + expect(formulae.dependency_name_match?(Dependency.new("homebrew/core/foo"), "homebrew/core/foo")) .to be(true) - expect(formulae.send(:dependency_name_match?, Dependency.new("homebrew/core/foo"), "foo")).to be(false) - expect(formulae.send(:dependency_name_match?, Dependency.new("homebrew/core/foo"), "user/tap/foo")) + expect(formulae.dependency_name_match?(Dependency.new("homebrew/core/foo"), "foo")).to be(false) + expect(formulae.dependency_name_match?(Dependency.new("homebrew/core/foo"), "user/tap/foo")) .to be(false) end end @@ -89,7 +89,7 @@ RSpec.describe Homebrew::TestBot::Formulae do ) with_env(GITHUB_ACTIONS: "true") do - expect { formulae.send(:annotate_added_dependencies, formula) } + expect { formulae.annotate_added_dependencies(formula) } .to output( "::warning file=#{formula.path.relative_path_from(CoreTap.instance.path)},line=7," \ "title=foo: new dependency impact::Adding `bar` adds 3 new recursive dependencies " \ @@ -181,7 +181,7 @@ RSpec.describe Homebrew::TestBot::Formulae do formulae = formulae_test_bot(tap_path, tmpdir) with_env(GITHUB_ACTIONS: "true", GITHUB_WORKSPACE: tap_path.to_s) do - expect { formulae.send(:annotate_missing_all_bottle, old_formula, bottle_dir: tap_path) } + expect { formulae.annotate_missing_all_bottle(old_formula, bottle_dir: tap_path) } .to output( "::warning file=Formula/foo.rb,line=8,title=foo: missing :all bottle::" \ "This formula had an `:all` bottle but the #{tag} test-bot bottle is platform-specific " \ @@ -208,7 +208,7 @@ RSpec.describe Homebrew::TestBot::Formulae do formulae = formulae_test_bot(tap_path, tmpdir) with_env(GITHUB_ACTIONS: "true", GITHUB_WORKSPACE: tap_path.to_s) do - expect { formulae.send(:annotate_missing_all_bottle, old_formula, bottle_dir: tap_path) } + expect { formulae.annotate_missing_all_bottle(old_formula, bottle_dir: tap_path) } .not_to output.to_stdout end end @@ -227,7 +227,7 @@ RSpec.describe Homebrew::TestBot::Formulae do formulae = formulae_test_bot(tap_path, tmpdir) with_env(GITHUB_ACTIONS: "true", GITHUB_WORKSPACE: tap_path.to_s) do - expect { formulae.send(:annotate_missing_all_bottle, old_formula, bottle_dir: tap_path) } + expect { formulae.annotate_missing_all_bottle(old_formula, bottle_dir: tap_path) } .not_to output.to_stdout end end @@ -248,7 +248,7 @@ RSpec.describe Homebrew::TestBot::Formulae do formulae = formulae_test_bot(tap_path, tmpdir) with_env(GITHUB_ACTIONS: "true", GITHUB_WORKSPACE: tap_path.to_s) do - expect { formulae.send(:annotate_missing_all_bottle, old_formula, bottle_dir: tap_path) } + expect { formulae.annotate_missing_all_bottle(old_formula, bottle_dir: tap_path) } .to output(/title=foo: missing :all bottle::.*sha256 `#{sha256}`/).to_stdout end end @@ -268,7 +268,7 @@ RSpec.describe Homebrew::TestBot::Formulae do formulae = formulae_test_bot(tap_path, tmpdir) with_env(GITHUB_ACTIONS: "true", GITHUB_WORKSPACE: tap_path.to_s) do - expect { formulae.send(:annotate_missing_all_bottle, old_formula, bottle_dir: tap_path) } + expect { formulae.annotate_missing_all_bottle(old_formula, bottle_dir: tap_path) } .not_to output.to_stdout end end @@ -290,7 +290,7 @@ RSpec.describe Homebrew::TestBot::Formulae do output_paths: ) - result = formulae.send(:testing_portable_ruby?) + result = formulae.testing_portable_ruby? expect(result).to be(false) end end @@ -312,7 +312,7 @@ RSpec.describe Homebrew::TestBot::Formulae do ) formulae.testing_formulae = ["portable-ruby"] - result = formulae.send(:verify_local_bottles) + result = formulae.verify_local_bottles expect(result).to be(false) end end @@ -349,7 +349,7 @@ RSpec.describe Homebrew::TestBot::Formulae do linkage: Pathname.new("#{tmpdir}/linkage.txt"), skipped_or_failed_formulae: Pathname.new("#{tmpdir}/skipped.txt"), } - ).send(:cleanup_bottle_etc_var, f) + ).cleanup_bottle_etc_var(f) expect([config_file.read, default_config_file.exist?]).to eq(["new\n", false]) ensure diff --git a/Library/Homebrew/test/test_bot/test_formulae_spec.rb b/Library/Homebrew/test/test_bot/test_formulae_spec.rb index f9a6e39925..457426f2cc 100644 --- a/Library/Homebrew/test/test_bot/test_formulae_spec.rb +++ b/Library/Homebrew/test/test_bot/test_formulae_spec.rb @@ -45,14 +45,13 @@ RSpec.describe Homebrew::TestBot::TestFormulae do Dir.mktmpdir do |tmpdir| Dir.chdir(tmpdir) do with_env("GITHUB_REPOSITORY" => "owner/repo") do - test_formulae.send(:download_artifacts_from_previous_run!, "bottles*", dry_run: false) + test_formulae.download_artifacts_from_previous_run!("bottles*", dry_run: false) end end end # Proves we passed the @downloaded_artifacts[sha] access for a new SHA without KeyError. - downloaded = test_formulae.instance_variable_get(:@downloaded_artifacts) - expect(downloaded[new_sha]).to include("bottles") + expect(test_formulae.downloaded_artifacts[new_sha]).to include("bottles") end end end diff --git a/Library/Homebrew/test/test_bot/test_spec.rb b/Library/Homebrew/test/test_bot/test_spec.rb index 3c2f6e6ad7..c77b0a6662 100644 --- a/Library/Homebrew/test/test_bot/test_spec.rb +++ b/Library/Homebrew/test/test_bot/test_spec.rb @@ -11,7 +11,7 @@ RSpec.describe Homebrew::TestBot::Test do # forwarding to Step.new, which expects T::Array[String]. test_instance = described_class.new(dry_run: true) - step = test_instance.send(:test, "git", "-C", Pathname.new("/some/path"), "status") + step = test_instance.test("git", "-C", Pathname.new("/some/path"), "status") expect(step.command).to eq(["git", "-C", "/some/path", "status"]) expect(step).to be_passed @@ -20,7 +20,7 @@ RSpec.describe Homebrew::TestBot::Test do it "allows nil environment values" do test_instance = described_class.new(dry_run: true) - step = test_instance.send(:test, "brew", "install", env: { "HOMEBREW_DEVELOPER" => nil }) + step = test_instance.test("brew", "install", env: { "HOMEBREW_DEVELOPER" => nil }) expect(step).to be_passed end diff --git a/Library/Homebrew/test/test_runner_formula_spec.rb b/Library/Homebrew/test/test_runner_formula_spec.rb index d6faf49c14..c4e05ba457 100644 --- a/Library/Homebrew/test/test_runner_formula_spec.rb +++ b/Library/Homebrew/test/test_runner_formula_spec.rb @@ -403,7 +403,7 @@ RSpec.describe TestRunnerFormula do dependencies.each { |dependency| depends_on dependency } kwargs.each do |k, v| - send(:"on_#{k}") do + public_send(:"on_#{k}") do v.each do |dep| depends_on dep end diff --git a/Library/Homebrew/test/unpack_strategy/dmg_spec.rb b/Library/Homebrew/test/unpack_strategy/dmg_spec.rb index 349847c4b1..ab9b31f57f 100644 --- a/Library/Homebrew/test/unpack_strategy/dmg_spec.rb +++ b/Library/Homebrew/test/unpack_strategy/dmg_spec.rb @@ -37,7 +37,7 @@ RSpec.describe UnpackStrategy::Dmg, :needs_macos do expect(attach_result).to receive(:assert_success!).and_raise(attach_error) expect(unpack_strategy).not_to receive(:system_command!) - expect { unpack_strategy.send(:mount) { nil } }.to raise_error(attach_error) + expect { unpack_strategy.mount { nil } }.to raise_error(attach_error) end end end diff --git a/Library/Homebrew/test/upgrade_spec.rb b/Library/Homebrew/test/upgrade_spec.rb index 8b2c7da24e..0b29c52dce 100644 --- a/Library/Homebrew/test/upgrade_spec.rb +++ b/Library/Homebrew/test/upgrade_spec.rb @@ -66,7 +66,7 @@ RSpec.describe Homebrew::Upgrade do FormulaInstaller, formula: Testball.new, compute_dependencies: [dependency] ) - expect { described_class.send(:upgrade_formula, formula_installer, dry_run: true) } + expect { described_class.upgrade_formula(formula_installer, dry_run: true) } .to output(/Would upgrade.*python@3.14 3.7.1 -> 3.14.6/m).to_stdout end end diff --git a/Library/Homebrew/test/utils/autoremove_spec.rb b/Library/Homebrew/test/utils/autoremove_spec.rb index 730054c80c..bd778f1895 100644 --- a/Library/Homebrew/test/utils/autoremove_spec.rb +++ b/Library/Homebrew/test/utils/autoremove_spec.rb @@ -79,7 +79,7 @@ RSpec.describe Utils::Autoremove do it "filters out runtime dependencies" do allow(tab_from_keg).to receive(:poured_from_bottle).and_return(true) - expect(described_class.send(:bottled_formulae_with_no_formula_dependents, formulae)) + expect(described_class.bottled_formulae_with_no_formula_dependents(formulae)) .to eq([formula_with_deps, formula_is_build_dep]) end end @@ -88,7 +88,7 @@ RSpec.describe Utils::Autoremove do it "filters out formulae" do allow(tab_from_keg).to receive(:poured_from_bottle).and_return(false) - expect(described_class.send(:bottled_formulae_with_no_formula_dependents, formulae)) + expect(described_class.bottled_formulae_with_no_formula_dependents(formulae)) .to eq([]) end end @@ -102,7 +102,7 @@ RSpec.describe Utils::Autoremove do expect(formula_with_deps).not_to receive(:installed_runtime_formula_dependencies) expect(first_formula_dep).not_to receive(:installed_runtime_formula_dependencies) - expect(described_class.send(:bottled_formulae_with_no_formula_dependents, formulae)) + expect(described_class.bottled_formulae_with_no_formula_dependents(formulae)) .to eq([formula_with_deps, formula_is_build_dep]) end end @@ -118,21 +118,21 @@ RSpec.describe Utils::Autoremove do specify "installed on request" do allow(tab_from_keg).to receive_messages(installed_on_request: true, installed_on_request_present?: true) - expect(described_class.send(:unused_formulae_with_no_formula_dependents, formulae)) + expect(described_class.unused_formulae_with_no_formula_dependents(formulae)) .to eq([]) end specify "not installed on request" do allow(tab_from_keg).to receive_messages(installed_on_request: false, installed_on_request_present?: true) - expect(described_class.send(:unused_formulae_with_no_formula_dependents, formulae)) + expect(described_class.unused_formulae_with_no_formula_dependents(formulae)) .to match_array(formulae) end specify "installed on request is null" do allow(tab_from_keg).to receive_messages(installed_on_request: false, installed_on_request_present?: false) - expect(described_class.send(:unused_formulae_with_no_formula_dependents, formulae)) + expect(described_class.unused_formulae_with_no_formula_dependents(formulae)) .to eq([]) end end @@ -181,17 +181,17 @@ RSpec.describe Utils::Autoremove do include_context "with formulae and casks for dependency testing" specify "no dependents" do - expect(described_class.send(:cask_dependent_formula_names, casks_no_deps, formulae)) + expect(described_class.cask_dependent_formula_names(casks_no_deps, formulae)) .to eq(Set.new) end specify "one dependent" do - expect(described_class.send(:cask_dependent_formula_names, casks_one_dep, formulae)) + expect(described_class.cask_dependent_formula_names(casks_one_dep, formulae)) .to contain_exactly("two") end specify "multiple dependents" do - expect(described_class.send(:cask_dependent_formula_names, casks_multiple_deps, formulae)) + expect(described_class.cask_dependent_formula_names(casks_multiple_deps, formulae)) .to contain_exactly("zero", "one", "two") end end diff --git a/Library/Homebrew/test/utils/bottles/collector_spec.rb b/Library/Homebrew/test/utils/bottles/collector_spec.rb index 7f77873dd1..70d80bb629 100644 --- a/Library/Homebrew/test/utils/bottles/collector_spec.rb +++ b/Library/Homebrew/test/utils/bottles/collector_spec.rb @@ -32,24 +32,24 @@ RSpec.describe Utils::Bottles::Collector do it "uses older tags when needed", :needs_macos do collector.add(sonoma, checksum: Checksum.new("foo_checksum"), cellar: "foo_cellar") - expect(collector.send(:find_matching_tag, sonoma)).to eq(sonoma) - expect(collector.send(:find_matching_tag, sequoia)).to eq(sonoma) + expect(collector.find_matching_tag(sonoma)).to eq(sonoma) + expect(collector.find_matching_tag(sequoia)).to eq(sonoma) end it "does not use older tags when requested not to", :needs_macos do allow(Homebrew::EnvConfig).to receive_messages(developer?: true, skip_or_later_bottles?: true) allow(OS::Mac.version).to receive(:prerelease?).and_return(true) collector.add(sonoma, checksum: Checksum.new("foo_checksum"), cellar: "foo_cellar") - expect(collector.send(:find_matching_tag, sonoma)).to eq(sonoma) - expect(collector.send(:find_matching_tag, sequoia)).to be_nil + expect(collector.find_matching_tag(sonoma)).to eq(sonoma) + expect(collector.find_matching_tag(sequoia)).to be_nil end it "ignores HOMEBREW_SKIP_OR_LATER_BOTTLES on release versions", :needs_macos do allow(Homebrew::EnvConfig).to receive(:skip_or_later_bottles?).and_return(true) allow(OS::Mac.version).to receive(:prerelease?).and_return(false) collector.add(sonoma, checksum: Checksum.new("foo_checksum"), cellar: "foo_cellar") - expect(collector.send(:find_matching_tag, sonoma)).to eq(sonoma) - expect(collector.send(:find_matching_tag, sequoia)).to eq(sonoma) + expect(collector.find_matching_tag(sonoma)).to eq(sonoma) + expect(collector.find_matching_tag(sequoia)).to eq(sonoma) end end end diff --git a/Library/Homebrew/test/utils/shell_spec.rb b/Library/Homebrew/test/utils/shell_spec.rb index 35edc4226d..7bc37ee15a 100644 --- a/Library/Homebrew/test/utils/shell_spec.rb +++ b/Library/Homebrew/test/utils/shell_spec.rb @@ -68,20 +68,20 @@ RSpec.describe Utils::Shell do end specify "::sh_quote" do - expect(described_class.send(:sh_quote, "")).to eq("''") - expect(described_class.send(:sh_quote, "\\")).to eq("\\\\") - expect(described_class.send(:sh_quote, "\n")).to eq("'\n'") - expect(described_class.send(:sh_quote, "$")).to eq("\\$") - expect(described_class.send(:sh_quote, "word")).to eq("word") + expect(described_class.sh_quote("")).to eq("''") + expect(described_class.sh_quote("\\")).to eq("\\\\") + expect(described_class.sh_quote("\n")).to eq("'\n'") + expect(described_class.sh_quote("$")).to eq("\\$") + expect(described_class.sh_quote("word")).to eq("word") end specify "::csh_quote" do - expect(described_class.send(:csh_quote, "")).to eq("''") - expect(described_class.send(:csh_quote, "\\")).to eq("\\\\") + expect(described_class.csh_quote("")).to eq("''") + expect(described_class.csh_quote("\\")).to eq("\\\\") # NOTE: This test is different than for `sh`. - expect(described_class.send(:csh_quote, "\n")).to eq("'\\\n'") - expect(described_class.send(:csh_quote, "$")).to eq("\\$") - expect(described_class.send(:csh_quote, "word")).to eq("word") + expect(described_class.csh_quote("\n")).to eq("'\\\n'") + expect(described_class.csh_quote("$")).to eq("\\$") + expect(described_class.csh_quote("word")).to eq("word") end describe "::prepend_path_in_profile" do diff --git a/Library/Homebrew/test/utils/svn_spec.rb b/Library/Homebrew/test/utils/svn_spec.rb index 2320d1c8c2..f5e74eae4f 100644 --- a/Library/Homebrew/test/utils/svn_spec.rb +++ b/Library/Homebrew/test/utils/svn_spec.rb @@ -12,8 +12,7 @@ RSpec.describe Utils::Svn do def clear_version_cache return unless described_class.instance_variable_defined?(:@version) - described_class.send(:remove_instance_variable, - :@version) + described_class.remove_instance_variable(:@version) end before do diff --git a/Library/Homebrew/test/utils/tar_spec.rb b/Library/Homebrew/test/utils/tar_spec.rb index 93e461c03c..27ac414ea4 100644 --- a/Library/Homebrew/test/utils/tar_spec.rb +++ b/Library/Homebrew/test/utils/tar_spec.rb @@ -7,8 +7,7 @@ RSpec.describe Utils::Tar do def clear_executable_cache return unless described_class.instance_variable_defined?(:@executable) - described_class.send(:remove_instance_variable, - :@executable) + described_class.remove_instance_variable(:@executable) end before do diff --git a/Library/Homebrew/test_bot/formulae.rb b/Library/Homebrew/test_bot/formulae.rb index 8e941de67f..1e52e46ee5 100644 --- a/Library/Homebrew/test_bot/formulae.rb +++ b/Library/Homebrew/test_bot/formulae.rb @@ -105,104 +105,6 @@ module Homebrew FileUtils.rm_rf artifact_cache end - private - - sig { void } - def install_ca_certificates_if_needed - return if DevelopmentTools.ca_file_handles_most_https_certificates? - - test "brew", "install", "--formulae", "ca-certificates", - env: { "HOMEBREW_DEVELOPER" => nil } - end - - sig { params(formula: Formula, formula_name: String, args: Homebrew::Cmd::TestBotCmd::Args).void } - def setup_formulae_deps_instances(formula, formula_name, args:) - conflicts = T.let(formula.conflicts, T::Array[T.any(Formula, Formula::FormulaConflict)]) - formula_recursive_dependencies = formula.recursive_dependencies.map(&:to_formula) - formula_recursive_dependencies.each do |dependency| - conflicts += dependency.conflicts - end - - # If we depend on a versioned formula, make sure to unlink any other - # installed versions to make sure that we use the right one. - versioned_dependencies = formula_recursive_dependencies.select(&:versioned_formula?) - versioned_dependencies.each do |dependency| - alternative_versions = dependency.versioned_formulae - - begin - unversioned_name = dependency.name.sub(/@\d+(\.\d+)*$/, "") - alternative_versions << Formula[unversioned_name] - rescue FormulaUnavailableError - nil - end - - unneeded_alternative_versions = alternative_versions - formula_recursive_dependencies - conflicts += unneeded_alternative_versions - end - - unlink_formulae = conflicts.map(&:name) - unlink_formulae.uniq.each do |name| - unlink_formula = Formulary.factory(name) - next unless unlink_formula.latest_version_installed? - next unless unlink_formula.linked_keg.exist? - - test "brew", "unlink", name - end - - info_header "Determining dependencies..." - installed = Utils.safe_popen_read("brew", "list", "--formula", "--full-name").split("\n") - dependencies = - Utils.safe_popen_read("brew", "deps", "--formula", - "--include-build", - "--include-test", - "--full-name", - formula_name) - .split("\n") - installed_dependencies = installed & dependencies - installed_dependencies.each do |name| - link_formula = Formulary.factory(name) - next if link_formula.keg_only? - next if link_formula.linked_keg.exist? - next if unlink_formulae.include?(name) - - test "brew", "link", name - end - - dependencies -= installed - @unchanged_dependencies = dependencies - @testing_formulae - unless @unchanged_dependencies.empty? - test "brew", "fetch", "--formulae", "--retry", - *@unchanged_dependencies - end - - changed_dependencies = dependencies - @unchanged_dependencies - unless changed_dependencies.empty? - test "brew", "fetch", "--formulae", "--retry", "--build-from-source", - *changed_dependencies - - ignore_failures = !args.test_default_formula? && changed_dependencies.any? do |dep| - !bottled?(Formulary.factory(dep), no_older_versions: true) - end - - # Install changed dependencies as new bottles so we don't have - # checksum problems. We have to install all `changed_dependencies` - # in one `brew install` command to make sure they are installed in - # the right order. - test("brew", "install", "--formulae", "--build-from-source", - named_args: changed_dependencies, - ignore_failures:) - # Run postinstall on them because the tested formula might depend on - # this step - test "brew", "postinstall", named_args: changed_dependencies, ignore_failures: - end - - runtime_or_test_dependencies = - Utils.safe_popen_read("brew", "deps", "--formula", "--include-test", formula_name) - .split("\n") - build_dependencies = dependencies - runtime_or_test_dependencies - @unchanged_build_dependencies = build_dependencies - @testing_formulae - end - sig { params(formula: Formula).void } def cleanup_bottle_etc_var(formula) # Restore bottled `etc`/`var` through `Formula#install_etc_var`, keeping @@ -260,95 +162,6 @@ module Homebrew end end - sig { params(formula: Formula, new_formula: T.nilable(T::Boolean), args: Homebrew::Cmd::TestBotCmd::Args).void } - def bottle_reinstall_formula(formula, new_formula, args:) - unless build_bottle?(formula, args:) - @bottle_filename = T.let(nil, T.nilable(Pathname)) - return - end - - root_url = args.root_url - - # GitHub Releases url - root_url ||= if tap.present? && !T.must(tap).core_tap? && !args.test_default_formula? - "#{T.must(tap).default_remote}/releases/download/#{formula.name}-#{formula.pkg_version}" - end - - setup_bottle_sudo_purge!(args:) - - bottle_args = ["--verbose", "--json", formula.full_name] - bottle_args << "--keep-old" if args.keep_old? && !new_formula - bottle_args << "--skip-relocation" if args.skip_relocation? - bottle_args << "--force-core-tap" if args.test_default_formula? - bottle_args << "--root-url=#{root_url}" if root_url - bottle_args << "--only-json-tab" if args.only_json_tab? - - verify_local_bottles - test "brew", "bottle", *bottle_args - bottle_step = steps.fetch(-1) - - if !bottle_step.passed? || !bottle_step.output? - failed formula.full_name, "bottling failed" unless args.dry_run? - return - end - - @bottle_filename = Pathname.new( - T.must(bottle_step.output) - .gsub(%r{.*(\./\S+#{HOMEBREW_BOTTLES_EXTNAME_REGEX}).*}om, '\1'), - ) - @bottle_json_filename = Pathname.new( - @bottle_filename.to_s.gsub(/\.(\d+\.)?tar\.gz$/, ".json"), - ) - - @bottle_checksums[@bottle_filename.realpath] = @bottle_filename.sha256 - @bottle_checksums[@bottle_json_filename.realpath] = @bottle_json_filename.sha256 - - @bottle_output_path.write(bottle_step.output, mode: "a") - - bottle_merge_args = - ["--merge", "--write", "--no-commit", "--no-all-checks", @bottle_json_filename.to_s] - bottle_merge_args << "--keep-old" if args.keep_old? && !new_formula - - test "brew", "bottle", *bottle_merge_args - annotate_missing_all_bottle(formula) if steps.fetch(-1).passed? - test "brew", "uninstall", "--formula", "--force", "--ignore-dependencies", formula.full_name - - @testing_formulae.delete(formula.name) - - unless @unchanged_build_dependencies.empty? - test "brew", "uninstall", "--formulae", "--force", "--ignore-dependencies", *@unchanged_build_dependencies - @unchanged_dependencies -= @unchanged_build_dependencies - end - - verify_attestations = if formula.name == "gh" - nil - else - ENV.fetch("HOMEBREW_VERIFY_ATTESTATIONS", nil) - end - test "brew", "install", "--only-dependencies", @bottle_filename.to_s, - env: { "HOMEBREW_VERIFY_ATTESTATIONS" => verify_attestations } - test "brew", "install", @bottle_filename.to_s, - env: { "HOMEBREW_VERIFY_ATTESTATIONS" => verify_attestations } - end - - sig { params(formula: Formula, args: Homebrew::Cmd::TestBotCmd::Args).returns(T::Boolean) } - def build_bottle?(formula, args:) - # Build and runtime dependencies must be bottled on the current OS, - # but accept an older compatible bottle for test dependencies. - return false if formula.deps.any? do |dep| - !bottled_or_built?( - dep.to_formula, - @built_formulae - @skipped_or_failed_formulae, - no_older_versions: !dep.test?, - ) - end - - !args.build_from_source? - end - - sig { params(args: Homebrew::Cmd::TestBotCmd::Args).void } - def setup_bottle_sudo_purge!(args:); end - sig { params(dependency: Dependency, dependency_name: String).returns(T::Boolean) } def dependency_name_match?(dependency, dependency_name) return true if dependency.name == dependency_name @@ -358,15 +171,6 @@ module Homebrew Utils.name_from_full_name(dependency.name) == Utils.name_from_full_name(dependency_name) end - sig { params(_formula: Formula, dependencies: T::Array[Dependency]).returns(T::Array[String]) } - def recursive_runtime_dependency_names(_formula, dependencies) - dependencies.each_with_object(Set.new) do |dep, set| - dep_f = dep.to_formula - set.add(dep_f.full_name) - set.merge(dep_f.runtime_dependencies(read_from_tab: false, undeclared: false).map(&:name)) - end.to_a - end - sig { params(formula: Formula).void } def annotate_added_dependencies(formula) return unless GitHub::Actions.env_set? @@ -517,6 +321,207 @@ module Homebrew opoo "Failed to determine missing `:all` bottle impact for #{formula.full_name}: #{e}" end + sig { returns(T::Boolean) } + def testing_portable_ruby? + !!tap&.core_tap? && @testing_formulae.include?("portable-ruby") + end + + private + + sig { void } + def install_ca_certificates_if_needed + return if DevelopmentTools.ca_file_handles_most_https_certificates? + + test "brew", "install", "--formulae", "ca-certificates", + env: { "HOMEBREW_DEVELOPER" => nil } + end + + sig { params(formula: Formula, formula_name: String, args: Homebrew::Cmd::TestBotCmd::Args).void } + def setup_formulae_deps_instances(formula, formula_name, args:) + conflicts = T.let(formula.conflicts, T::Array[T.any(Formula, Formula::FormulaConflict)]) + formula_recursive_dependencies = formula.recursive_dependencies.map(&:to_formula) + formula_recursive_dependencies.each do |dependency| + conflicts += dependency.conflicts + end + + # If we depend on a versioned formula, make sure to unlink any other + # installed versions to make sure that we use the right one. + versioned_dependencies = formula_recursive_dependencies.select(&:versioned_formula?) + versioned_dependencies.each do |dependency| + alternative_versions = dependency.versioned_formulae + + begin + unversioned_name = dependency.name.sub(/@\d+(\.\d+)*$/, "") + alternative_versions << Formula[unversioned_name] + rescue FormulaUnavailableError + nil + end + + unneeded_alternative_versions = alternative_versions - formula_recursive_dependencies + conflicts += unneeded_alternative_versions + end + + unlink_formulae = conflicts.map(&:name) + unlink_formulae.uniq.each do |name| + unlink_formula = Formulary.factory(name) + next unless unlink_formula.latest_version_installed? + next unless unlink_formula.linked_keg.exist? + + test "brew", "unlink", name + end + + info_header "Determining dependencies..." + installed = Utils.safe_popen_read("brew", "list", "--formula", "--full-name").split("\n") + dependencies = + Utils.safe_popen_read("brew", "deps", "--formula", + "--include-build", + "--include-test", + "--full-name", + formula_name) + .split("\n") + installed_dependencies = installed & dependencies + installed_dependencies.each do |name| + link_formula = Formulary.factory(name) + next if link_formula.keg_only? + next if link_formula.linked_keg.exist? + next if unlink_formulae.include?(name) + + test "brew", "link", name + end + + dependencies -= installed + @unchanged_dependencies = dependencies - @testing_formulae + unless @unchanged_dependencies.empty? + test "brew", "fetch", "--formulae", "--retry", + *@unchanged_dependencies + end + + changed_dependencies = dependencies - @unchanged_dependencies + unless changed_dependencies.empty? + test "brew", "fetch", "--formulae", "--retry", "--build-from-source", + *changed_dependencies + + ignore_failures = !args.test_default_formula? && changed_dependencies.any? do |dep| + !bottled?(Formulary.factory(dep), no_older_versions: true) + end + + # Install changed dependencies as new bottles so we don't have + # checksum problems. We have to install all `changed_dependencies` + # in one `brew install` command to make sure they are installed in + # the right order. + test("brew", "install", "--formulae", "--build-from-source", + named_args: changed_dependencies, + ignore_failures:) + # Run postinstall on them because the tested formula might depend on + # this step + test "brew", "postinstall", named_args: changed_dependencies, ignore_failures: + end + + runtime_or_test_dependencies = + Utils.safe_popen_read("brew", "deps", "--formula", "--include-test", formula_name) + .split("\n") + build_dependencies = dependencies - runtime_or_test_dependencies + @unchanged_build_dependencies = build_dependencies - @testing_formulae + end + + sig { params(formula: Formula, new_formula: T.nilable(T::Boolean), args: Homebrew::Cmd::TestBotCmd::Args).void } + def bottle_reinstall_formula(formula, new_formula, args:) + unless build_bottle?(formula, args:) + @bottle_filename = T.let(nil, T.nilable(Pathname)) + return + end + + root_url = args.root_url + + # GitHub Releases url + root_url ||= if tap.present? && !T.must(tap).core_tap? && !args.test_default_formula? + "#{T.must(tap).default_remote}/releases/download/#{formula.name}-#{formula.pkg_version}" + end + + setup_bottle_sudo_purge!(args:) + + bottle_args = ["--verbose", "--json", formula.full_name] + bottle_args << "--keep-old" if args.keep_old? && !new_formula + bottle_args << "--skip-relocation" if args.skip_relocation? + bottle_args << "--force-core-tap" if args.test_default_formula? + bottle_args << "--root-url=#{root_url}" if root_url + bottle_args << "--only-json-tab" if args.only_json_tab? + + verify_local_bottles + test "brew", "bottle", *bottle_args + bottle_step = steps.fetch(-1) + + if !bottle_step.passed? || !bottle_step.output? + failed formula.full_name, "bottling failed" unless args.dry_run? + return + end + + @bottle_filename = Pathname.new( + T.must(bottle_step.output) + .gsub(%r{.*(\./\S+#{HOMEBREW_BOTTLES_EXTNAME_REGEX}).*}om, '\1'), + ) + @bottle_json_filename = Pathname.new( + @bottle_filename.to_s.gsub(/\.(\d+\.)?tar\.gz$/, ".json"), + ) + + @bottle_checksums[@bottle_filename.realpath] = @bottle_filename.sha256 + @bottle_checksums[@bottle_json_filename.realpath] = @bottle_json_filename.sha256 + + @bottle_output_path.write(bottle_step.output, mode: "a") + + bottle_merge_args = + ["--merge", "--write", "--no-commit", "--no-all-checks", @bottle_json_filename.to_s] + bottle_merge_args << "--keep-old" if args.keep_old? && !new_formula + + test "brew", "bottle", *bottle_merge_args + annotate_missing_all_bottle(formula) if steps.fetch(-1).passed? + test "brew", "uninstall", "--formula", "--force", "--ignore-dependencies", formula.full_name + + @testing_formulae.delete(formula.name) + + unless @unchanged_build_dependencies.empty? + test "brew", "uninstall", "--formulae", "--force", "--ignore-dependencies", *@unchanged_build_dependencies + @unchanged_dependencies -= @unchanged_build_dependencies + end + + verify_attestations = if formula.name == "gh" + nil + else + ENV.fetch("HOMEBREW_VERIFY_ATTESTATIONS", nil) + end + test "brew", "install", "--only-dependencies", @bottle_filename.to_s, + env: { "HOMEBREW_VERIFY_ATTESTATIONS" => verify_attestations } + test "brew", "install", @bottle_filename.to_s, + env: { "HOMEBREW_VERIFY_ATTESTATIONS" => verify_attestations } + end + + sig { params(formula: Formula, args: Homebrew::Cmd::TestBotCmd::Args).returns(T::Boolean) } + def build_bottle?(formula, args:) + # Build and runtime dependencies must be bottled on the current OS, + # but accept an older compatible bottle for test dependencies. + return false if formula.deps.any? do |dep| + !bottled_or_built?( + dep.to_formula, + @built_formulae - @skipped_or_failed_formulae, + no_older_versions: !dep.test?, + ) + end + + !args.build_from_source? + end + + sig { params(args: Homebrew::Cmd::TestBotCmd::Args).void } + def setup_bottle_sudo_purge!(args:); end + + sig { params(_formula: Formula, dependencies: T::Array[Dependency]).returns(T::Array[String]) } + def recursive_runtime_dependency_names(_formula, dependencies) + dependencies.each_with_object(Set.new) do |dep, set| + dep_f = dep.to_formula + set.add(dep_f.full_name) + set.merge(dep_f.runtime_dependencies(read_from_tab: false, undeclared: false).map(&:name)) + end.to_a + end + sig { params(formula_name: String, bottle_dir: Pathname) .returns(T::Hash[String, T::Hash[String, T.anything]]) @@ -952,11 +957,6 @@ module Homebrew env: require_current_tap_trust_env end - sig { returns(T::Boolean) } - def testing_portable_ruby? - !!tap&.core_tap? && @testing_formulae.include?("portable-ruby") - end - sig { returns(T::Boolean) } def integration_test_portable_ruby? = true end diff --git a/Library/Homebrew/test_bot/formulae_dependents.rb b/Library/Homebrew/test_bot/formulae_dependents.rb index 020089777f..7a306cf757 100644 --- a/Library/Homebrew/test_bot/formulae_dependents.rb +++ b/Library/Homebrew/test_bot/formulae_dependents.rb @@ -90,6 +90,77 @@ module Homebrew test "brew", "uninstall", "--formula", "--force", "bash" end + sig { + params( + dependents: T::Array[DependentWithDependencies], + shard: String, + ).returns(T::Array[DependentWithDependencies]) + } + def dependents_for_shard(dependents, shard) + unless shard.match?(%r{\A[1-9]\d*/[1-9]\d*\z}) + raise UsageError, "`--formulae-dependents-shard` must use the format ." + end + + shard_parts = shard.split("/", 2) + shard_index = shard_parts.fetch(0).to_i + shard_count = shard_parts.fetch(1).to_i + if shard_index > shard_count + raise UsageError, "`--formulae-dependents-shard` must not be greater than the total shard count." + end + + return dependents if shard_count == 1 + + dependents_by_name = dependents.to_h { |dependent, deps| [dependent.full_name, [dependent, deps]] } + edges = dependents.to_h { |dependent, _| [dependent.full_name, T.let([], T::Array[String])] } + + dependents.each do |dependent, deps| + deps.each do |dep| + dep_name = dep.to_formula.full_name + next unless edges.key?(dep_name) + + edges.fetch(dependent.full_name) << dep_name + edges.fetch(dep_name) << dependent.full_name + end + end + + seen = T.let(Set.new, T::Set[String]) + groups = T.let([], T::Array[T::Array[DependentWithDependencies]]) + max_group_size = (dependents.size + shard_count - 1) / shard_count + + dependents.map(&:first).each do |dependent| + next if seen.include?(dependent.full_name) + + group = T.let([], T::Array[DependentWithDependencies]) + queue = T.let([dependent.full_name], T::Array[String]) + + until queue.empty? + name = queue.fetch(0) + queue.shift + next if seen.include?(name) + + seen << name + group << dependents_by_name.fetch(name) + break if group.size >= max_group_size + + queue.concat(edges.fetch(name).reject { |edge| seen.include?(edge) }) + end + + groups << group + end + + shards = Array.new(shard_count) { T.let([], T::Array[DependentWithDependencies]) } + groups.sort_by { |group| [-group.count, group.map { |dependent, _| dependent.full_name }.min.to_s] } + .each do |group| + group_shard_index = 0 + shards.each_with_index do |current_shard, index| + group_shard_index = index if current_shard.count < shards.fetch(group_shard_index).count + end + shards.fetch(group_shard_index).concat(group) + end + + shards.fetch(shard_index - 1).sort_by { |dependent, _| dependent.full_name } + end + private sig { params(installable_bottles: T::Array[String], args: Homebrew::Cmd::TestBotCmd::Args).void } @@ -273,77 +344,6 @@ module Homebrew end end - sig { - params( - dependents: T::Array[DependentWithDependencies], - shard: String, - ).returns(T::Array[DependentWithDependencies]) - } - def dependents_for_shard(dependents, shard) - unless shard.match?(%r{\A[1-9]\d*/[1-9]\d*\z}) - raise UsageError, "`--formulae-dependents-shard` must use the format ." - end - - shard_parts = shard.split("/", 2) - shard_index = shard_parts.fetch(0).to_i - shard_count = shard_parts.fetch(1).to_i - if shard_index > shard_count - raise UsageError, "`--formulae-dependents-shard` must not be greater than the total shard count." - end - - return dependents if shard_count == 1 - - dependents_by_name = dependents.to_h { |dependent, deps| [dependent.full_name, [dependent, deps]] } - edges = dependents.to_h { |dependent, _| [dependent.full_name, T.let([], T::Array[String])] } - - dependents.each do |dependent, deps| - deps.each do |dep| - dep_name = dep.to_formula.full_name - next unless edges.key?(dep_name) - - edges.fetch(dependent.full_name) << dep_name - edges.fetch(dep_name) << dependent.full_name - end - end - - seen = T.let(Set.new, T::Set[String]) - groups = T.let([], T::Array[T::Array[DependentWithDependencies]]) - max_group_size = (dependents.size + shard_count - 1) / shard_count - - dependents.map(&:first).each do |dependent| - next if seen.include?(dependent.full_name) - - group = T.let([], T::Array[DependentWithDependencies]) - queue = T.let([dependent.full_name], T::Array[String]) - - until queue.empty? - name = queue.fetch(0) - queue.shift - next if seen.include?(name) - - seen << name - group << dependents_by_name.fetch(name) - break if group.size >= max_group_size - - queue.concat(edges.fetch(name).reject { |edge| seen.include?(edge) }) - end - - groups << group - end - - shards = Array.new(shard_count) { T.let([], T::Array[DependentWithDependencies]) } - groups.sort_by { |group| [-group.count, group.map { |dependent, _| dependent.full_name }.min.to_s] } - .each do |group| - group_shard_index = 0 - shards.each_with_index do |current_shard, index| - group_shard_index = index if current_shard.count < shards.fetch(group_shard_index).count - end - shards.fetch(group_shard_index).concat(group) - end - - shards.fetch(shard_index - 1).sort_by { |dependent, _| dependent.full_name } - end - sig { params( dependent: Formula, diff --git a/Library/Homebrew/test_bot/test.rb b/Library/Homebrew/test_bot/test.rb index 20d55a039b..5c39f45b0e 100644 --- a/Library/Homebrew/test_bot/test.rb +++ b/Library/Homebrew/test_bot/test.rb @@ -22,6 +22,36 @@ module Homebrew sig { returns(T::Array[Step]) } attr_reader :steps + sig { + params( + arguments: T.any(String, Pathname), + named_args: T.nilable(T.any(String, T::Array[String])), + env: T::Hash[String, T.nilable(T.any(String, T::Boolean, PATH))], + verbose: T::Boolean, + ignore_failures: T::Boolean, + report_analytics: T::Boolean, + ).returns(Step) + } + def test(*arguments, named_args: nil, env: {}, verbose: @verbose, ignore_failures: false, + report_analytics: false) + step = Step.new( + arguments.map(&:to_s), + named_args:, + env:, + verbose:, + ignore_failures:, + repository: @repository, + ) + step.run(dry_run: @dry_run, fail_fast: @fail_fast) + @steps << step + + if ENV["HOMEBREW_TEST_BOT_ANALYTICS"].present? && report_analytics + ::Utils::Analytics.report_test_bot_test(step.command_short, step.passed?) + end + + step + end + protected sig { params(args: Homebrew::Cmd::TestBotCmd::Args).returns(T::Boolean) } @@ -77,36 +107,6 @@ module Homebrew def info_header(text) puts Formatter.headline(text, color: :cyan) end - - sig { - params( - arguments: T.any(String, Pathname), - named_args: T.nilable(T.any(String, T::Array[String])), - env: T::Hash[String, T.nilable(T.any(String, T::Boolean, PATH))], - verbose: T::Boolean, - ignore_failures: T::Boolean, - report_analytics: T::Boolean, - ).returns(Step) - } - def test(*arguments, named_args: nil, env: {}, verbose: @verbose, ignore_failures: false, - report_analytics: false) - step = Step.new( - arguments.map(&:to_s), - named_args:, - env:, - verbose:, - ignore_failures:, - repository: @repository, - ) - step.run(dry_run: @dry_run, fail_fast: @fail_fast) - @steps << step - - if ENV["HOMEBREW_TEST_BOT_ANALYTICS"].present? && report_analytics - ::Utils::Analytics.report_test_bot_test(step.command_short, step.passed?) - end - - step - end end end end diff --git a/Library/Homebrew/test_bot/test_formulae.rb b/Library/Homebrew/test_bot/test_formulae.rb index 0a9a63a216..b98583e19a 100644 --- a/Library/Homebrew/test_bot/test_formulae.rb +++ b/Library/Homebrew/test_bot/test_formulae.rb @@ -12,6 +12,9 @@ module Homebrew sig { returns(Pathname) } attr_reader :artifact_cache + sig { returns(T::Hash[String, T::Array[String]]) } + attr_reader :downloaded_artifacts + sig { params( tap: T.nilable(Tap), @@ -33,6 +36,80 @@ module Homebrew @tested_formulae = T.let([], T::Array[String]) end + sig { params(artifact_pattern: String, dry_run: T::Boolean).void } + def download_artifacts_from_previous_run!(artifact_pattern, dry_run:) + return if dry_run + return if GitHub::API.credentials_type == :none + return if (sha = previous_github_sha).blank? + + pull_number = github_event_payload&.dig("pull_request", "number") + return if pull_number.blank? + + github_repository = ENV.fetch("GITHUB_REPOSITORY") + owner, repo = *github_repository.split("/") + raise "github_repository #{github_repository} is invalid" if owner.nil? || repo.nil? + + pr_labels = GitHub.pull_request_labels(owner, repo, pull_number) + # Also disable bottle cache for PRs modifying workflows to avoid cache poisoning. + return if pr_labels.include?("CI-no-bottle-cache") || pr_labels.include?("workflows") + + variables = { + owner:, + repo:, + commit: sha, + } + + response = GitHub::API.open_graphql(GRAPHQL_QUERY, variables:) + check_suite_nodes = response.dig("repository", "object", "checkSuites", "nodes") + return if check_suite_nodes.blank? + + wanted_artifacts = artifact_metadata(check_suite_nodes, github_repository, "pull_request", + "CI", "conclusion", artifact_pattern) + wanted_artifacts_pattern = artifact_pattern + if wanted_artifacts.empty? + # If we didn't find the artifacts that we wanted, fall back to the `event_payload` artifact. + wanted_artifacts = artifact_metadata(check_suite_nodes, github_repository, "pull_request_target", + "Triage tasks", "upload-metadata", "event_payload") + wanted_artifacts_pattern = "event_payload" + end + return if wanted_artifacts.empty? + + if (attempted_artifact = wanted_artifacts.find do |artifact| + # Hash value must exist due to the hash having a default value of an empty array. + T.must(@downloaded_artifacts[sha]).include?(artifact.fetch("name")) + end) + opoo "Already tried #{attempted_artifact.fetch("name")} from #{sha}, giving up" + return + end + + cached_event_json&.unlink if File.fnmatch?(wanted_artifacts_pattern, "event_payload", File::FNM_EXTGLOB) + + require "utils/github/artifacts" + + ohai "Downloading artifacts matching pattern #{wanted_artifacts_pattern} from #{sha}" + artifact_cache.mkpath + artifact_cache.cd do + wanted_artifacts.each do |artifact| + name = artifact.fetch("name") + ohai "Downloading artifact #{name} from #{sha}" + # Hash value must exist due to the hash having a default value of an empty array. + T.must(@downloaded_artifacts[sha]) << name + + download_url = artifact.fetch("archive_download_url") + artifact_id = artifact.fetch("id") + GitHub.download_artifact(download_url, artifact_id.to_s) + end + end + + return if wanted_artifacts_pattern == artifact_pattern + + # If we made it here, then we downloaded an `event_payload` artifact. + # We can now use this `event_payload` artifact to attempt to download the artifact we wanted. + download_artifacts_from_previous_run!(artifact_pattern, dry_run:) + rescue GitHub::API::AuthenticationFailedError => e + opoo e + end + protected sig { returns(T::Hash[String, String]) } @@ -146,80 +223,6 @@ module Homebrew } GRAPHQL - sig { params(artifact_pattern: String, dry_run: T::Boolean).void } - def download_artifacts_from_previous_run!(artifact_pattern, dry_run:) - return if dry_run - return if GitHub::API.credentials_type == :none - return if (sha = previous_github_sha).blank? - - pull_number = github_event_payload&.dig("pull_request", "number") - return if pull_number.blank? - - github_repository = ENV.fetch("GITHUB_REPOSITORY") - owner, repo = *github_repository.split("/") - raise "github_repository #{github_repository} is invalid" if owner.nil? || repo.nil? - - pr_labels = GitHub.pull_request_labels(owner, repo, pull_number) - # Also disable bottle cache for PRs modifying workflows to avoid cache poisoning. - return if pr_labels.include?("CI-no-bottle-cache") || pr_labels.include?("workflows") - - variables = { - owner:, - repo:, - commit: sha, - } - - response = GitHub::API.open_graphql(GRAPHQL_QUERY, variables:) - check_suite_nodes = response.dig("repository", "object", "checkSuites", "nodes") - return if check_suite_nodes.blank? - - wanted_artifacts = artifact_metadata(check_suite_nodes, github_repository, "pull_request", - "CI", "conclusion", artifact_pattern) - wanted_artifacts_pattern = artifact_pattern - if wanted_artifacts.empty? - # If we didn't find the artifacts that we wanted, fall back to the `event_payload` artifact. - wanted_artifacts = artifact_metadata(check_suite_nodes, github_repository, "pull_request_target", - "Triage tasks", "upload-metadata", "event_payload") - wanted_artifacts_pattern = "event_payload" - end - return if wanted_artifacts.empty? - - if (attempted_artifact = wanted_artifacts.find do |artifact| - # Hash value must exist due to the hash having a default value of an empty array. - T.must(@downloaded_artifacts[sha]).include?(artifact.fetch("name")) - end) - opoo "Already tried #{attempted_artifact.fetch("name")} from #{sha}, giving up" - return - end - - cached_event_json&.unlink if File.fnmatch?(wanted_artifacts_pattern, "event_payload", File::FNM_EXTGLOB) - - require "utils/github/artifacts" - - ohai "Downloading artifacts matching pattern #{wanted_artifacts_pattern} from #{sha}" - artifact_cache.mkpath - artifact_cache.cd do - wanted_artifacts.each do |artifact| - name = artifact.fetch("name") - ohai "Downloading artifact #{name} from #{sha}" - # Hash value must exist due to the hash having a default value of an empty array. - T.must(@downloaded_artifacts[sha]) << name - - download_url = artifact.fetch("archive_download_url") - artifact_id = artifact.fetch("id") - GitHub.download_artifact(download_url, artifact_id.to_s) - end - end - - return if wanted_artifacts_pattern == artifact_pattern - - # If we made it here, then we downloaded an `event_payload` artifact. - # We can now use this `event_payload` artifact to attempt to download the artifact we wanted. - download_artifacts_from_previous_run!(artifact_pattern, dry_run:) - rescue GitHub::API::AuthenticationFailedError => e - opoo e - end - sig { params(formula: Formula, git_ref: String).returns(T::Boolean) } def no_diff?(formula, git_ref) return false unless repository.directory? diff --git a/Library/Homebrew/unpack_strategy/dmg.rb b/Library/Homebrew/unpack_strategy/dmg.rb index d7ebe514be..b2188041c9 100644 --- a/Library/Homebrew/unpack_strategy/dmg.rb +++ b/Library/Homebrew/unpack_strategy/dmg.rb @@ -183,19 +183,6 @@ module UnpackStrategy (status.success? && !stdout.empty?) || false end - private - - sig { override.params(unpack_dir: Pathname, basename: Pathname, verbose: T::Boolean).void } - def extract_to_dir(unpack_dir, basename:, verbose:) - mount(verbose:) do |mounts| - raise "No mounts found in '#{path}'; perhaps this is a bad disk image?" if mounts.empty? - - mounts.each do |mount| - mount.extract(to: unpack_dir, verbose:) - end - end - end - sig { params(verbose: T::Boolean, _block: T.proc.params(arg0: T::Array[Mount]).void).void } def mount(verbose: false, &_block) Dir.mktmpdir("homebrew-dmg", HOMEBREW_TEMP) do |mount_dir| @@ -263,5 +250,18 @@ module UnpackStrategy end end end + + private + + sig { override.params(unpack_dir: Pathname, basename: Pathname, verbose: T::Boolean).void } + def extract_to_dir(unpack_dir, basename:, verbose:) + mount(verbose:) do |mounts| + raise "No mounts found in '#{path}'; perhaps this is a bad disk image?" if mounts.empty? + + mounts.each do |mount| + mount.extract(to: unpack_dir, verbose:) + end + end + end end end diff --git a/Library/Homebrew/upgrade.rb b/Library/Homebrew/upgrade.rb index 9a974668d1..c8e55922ba 100644 --- a/Library/Homebrew/upgrade.rb +++ b/Library/Homebrew/upgrade.rb @@ -445,8 +445,6 @@ module Homebrew end end - private - sig { params(formula_installer: FormulaInstaller, dry_run: T::Boolean, verbose: T::Boolean, skip_formula_names: T::Array[String]).returns(T::Boolean) @@ -481,6 +479,8 @@ module Homebrew false end + private + sig { params(installed_formulae: T::Array[Formula]).returns(T::Array[Formula]) } def check_broken_dependents(installed_formulae) CacheStoreDatabase.use(:linkage) do |db| diff --git a/Library/Homebrew/utils/autoremove.rb b/Library/Homebrew/utils/autoremove.rb index 72f58a1fe5..1c3c2073b7 100644 --- a/Library/Homebrew/utils/autoremove.rb +++ b/Library/Homebrew/utils/autoremove.rb @@ -18,8 +18,6 @@ module Utils unused_formulae.reject { |f| cask_dep_names.intersect?(f.possible_names) } end - private - # A set of names for all installed {Formula} objects that are {Cask} formula # dependencies (direct or transitive). # @private diff --git a/Library/Homebrew/utils/bottles.rb b/Library/Homebrew/utils/bottles.rb index 6115d14584..a240d6c67a 100644 --- a/Library/Homebrew/utils/bottles.rb +++ b/Library/Homebrew/utils/bottles.rb @@ -386,13 +386,6 @@ module Utils @tag_specs[tag] if tag end - protected - - sig { returns(T::Hash[Utils::Bottles::Tag, Utils::Bottles::TagSpecification]) } - attr_reader :tag_specs - - private - sig { params(tag: Utils::Bottles::Tag, no_older_versions: T::Boolean).returns(T.nilable(Utils::Bottles::Tag)) } def find_matching_tag(tag, no_older_versions: false) if @tag_specs.key?(tag) @@ -402,6 +395,11 @@ module Utils all if @tag_specs.key?(all) end end + + protected + + sig { returns(T::Hash[Utils::Bottles::Tag, Utils::Bottles::TagSpecification]) } + attr_reader :tag_specs end end end