From 85a29baceedd829bb8e6457d15f6d417957fa73d Mon Sep 17 00:00:00 2001 From: Mike McQuaid Date: Sat, 23 May 2026 08:14:00 +0100 Subject: [PATCH] Speed up slow specs - Reduce slow command specs because repeated `brew` subprocesses were dominating the profile more than the tested Homebrew behaviour. - Preserve coverage by moving option and error branches into direct command-object specs where a full process adds no useful signal. - Keep shell-specific coverage by folding Bash command checks into one subprocess harness for each affected Bash path. - Combine formula and cask happy paths in `fetch`, `outdated` and `upgrade`, so one process still checks both package types. - Keep `cmd/install` source, bottle, keg-only and `--HEAD` coverage, with `--HEAD` separate because the option applies to the whole call. - Stub expensive `svn`, DMG mount and signal-wait paths so the specs cover Homebrew decisions without invoking slow external work. - Restore `brew extract` monkey patches when specs run in-process, so dependency and formula requirement caches do not leak across examples. - Document the integration-test limit in `AGENTS.md` so future command specs bias towards fast in-process coverage. --- AGENTS.md | 2 +- Library/Homebrew/dev-cmd/extract.rb | 57 ++-- .../artifact/shared_examples/uninstall_zap.rb | 1 + Library/Homebrew/test/cask/installer_spec.rb | 10 + Library/Homebrew/test/cask/upgrade_spec.rb | 21 +- Library/Homebrew/test/cmd/--cache_spec.rb | 19 +- Library/Homebrew/test/cmd/--caskroom_spec.rb | 15 +- Library/Homebrew/test/cmd/--cellar_spec.rb | 11 +- Library/Homebrew/test/cmd/--prefix_spec.rb | 37 ++- .../Homebrew/test/cmd/--repository_spec.rb | 40 +-- Library/Homebrew/test/cmd/desc_spec.rb | 29 +- Library/Homebrew/test/cmd/fetch_spec.rb | 14 +- Library/Homebrew/test/cmd/home_spec.rb | 48 ++-- Library/Homebrew/test/cmd/info_spec.rb | 41 +-- Library/Homebrew/test/cmd/install_spec.rb | 87 ++---- Library/Homebrew/test/cmd/leaves_spec.rb | 61 +++-- Library/Homebrew/test/cmd/link_spec.rb | 33 ++- Library/Homebrew/test/cmd/list_spec.rb | 249 ++++++++---------- Library/Homebrew/test/cmd/outdated_spec.rb | 76 +++--- Library/Homebrew/test/cmd/pin_spec.rb | 21 +- Library/Homebrew/test/cmd/tap-info_spec.rb | 8 +- Library/Homebrew/test/cmd/unalias_spec.rb | 13 +- Library/Homebrew/test/cmd/unpin_spec.rb | 12 +- Library/Homebrew/test/cmd/untap_spec.rb | 11 +- Library/Homebrew/test/cmd/upgrade_spec.rb | 128 +++++---- Library/Homebrew/test/cmd/uses_spec.rb | 15 +- .../Homebrew/test/cmd/which-formula_spec.rb | 75 ++++-- Library/Homebrew/test/dev-cmd/bump_spec.rb | 10 +- Library/Homebrew/test/dev-cmd/extract_spec.rb | 35 +-- Library/Homebrew/test/dev-cmd/formula_spec.rb | 4 +- Library/Homebrew/test/dev-cmd/linkage_spec.rb | 12 +- .../Homebrew/test/dev-cmd/livecheck_spec.rb | 14 +- Library/Homebrew/test/dev-cmd/ruby_spec.rb | 25 +- Library/Homebrew/test/dev-cmd/unpack_spec.rb | 6 +- .../download_strategies/subversion_spec.rb | 9 + .../test/installed_dependents_spec.rb | 26 +- Library/Homebrew/test/spec_helper.rb | 40 ++- .../Homebrew/test/unpack_strategy/dmg_spec.rb | 17 +- Library/Homebrew/test/utils/svn_spec.rb | 44 ++-- 39 files changed, 807 insertions(+), 569 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 4ec02a4d89a9a..82c13820bb9c5 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -27,7 +27,7 @@ When running Ruby directly (e.g. `ruby -e ...`, `gem`, profiling tools), never u ### Development Flow - Write new code (using Sorbet `sig` type signatures and `typed: strict` for new files). -- Write new tests (avoid more than one `:integration_test` per command for speed; add another only for essential core functionality in essential non-developer commands). Try `typed: true` as a baseline but revert to `typed: false` if there are not easily fixable errors. +- Write new tests (use at most one `:integration_test` per command, make it a happy-path test and keep it as fast as possible; add another only for essential core functionality in essential non-developer commands). Try `typed: true` as a baseline but revert to `typed: false` if there are not easily fixable errors. Write fast tests by preferring a single `expect` per unit test and combine expectations in a single test when it is an integration test or has non-trivial `before` for test setup. - When adding or tightening tests, verify them with a red/green cycle using the exact `--only=file:line` target for the example you changed. - Formula classes created in specs may be frozen; avoid stubbing class methods on them with RSpec mocks and prefer instance-level stubs or test setup that does not require class-method stubbing. diff --git a/Library/Homebrew/dev-cmd/extract.rb b/Library/Homebrew/dev-cmd/extract.rb index f9f1d8b79adeb..56a3c498cb77e 100644 --- a/Library/Homebrew/dev-cmd/extract.rb +++ b/Library/Homebrew/dev-cmd/extract.rb @@ -174,67 +174,86 @@ def formula_at_revision(repo, name, file, rev) sig { params(_block: T.proc.void).returns(T.untyped) } def with_monkey_patch(&_block) - # Since `method_defined?` is not a supported type guard, the use of `alias_method` below is not typesafe: + DependencyCollector.clear_cache + BottleSpecification.class_eval do - T.unsafe(self).alias_method :old_method_missing, :method_missing if method_defined?(:method_missing) + if method_defined?(:method_missing) || private_method_defined?(:method_missing) + send(:alias_method, :old_method_missing, :method_missing) + send(:private, :old_method_missing) + end define_method(:method_missing) do |*_| # do nothing end + send(:private, :method_missing) end Module.class_eval do - T.unsafe(self).alias_method :old_method_missing, :method_missing if method_defined?(:method_missing) + if method_defined?(:method_missing) || private_method_defined?(:method_missing) + send(:alias_method, :old_method_missing, :method_missing) + send(:private, :old_method_missing) + end define_method(:method_missing) do |*_| # do nothing end + send(:private, :method_missing) end Resource.class_eval do - T.unsafe(self).alias_method :old_method_missing, :method_missing if method_defined?(:method_missing) + if method_defined?(:method_missing) || private_method_defined?(:method_missing) + send(:alias_method, :old_method_missing, :method_missing) + send(:private, :old_method_missing) + end define_method(:method_missing) do |*_| # do nothing end + send(:private, :method_missing) end DependencyCollector.class_eval do - if method_defined?(:parse_symbol_spec) - T.unsafe(self).alias_method :old_parse_symbol_spec, - :parse_symbol_spec + if method_defined?(:parse_symbol_spec) || private_method_defined?(:parse_symbol_spec) + send(:alias_method, :old_parse_symbol_spec, :parse_symbol_spec) + send(:private, :old_parse_symbol_spec) end define_method(:parse_symbol_spec) do |*_| # do nothing end + send(:private, :parse_symbol_spec) end yield ensure BottleSpecification.class_eval do - if method_defined?(:old_method_missing) - T.unsafe(self).alias_method :method_missing, :old_method_missing - T.unsafe(self).undef :old_method_missing + if method_defined?(:old_method_missing) || private_method_defined?(:old_method_missing) + send(:alias_method, :method_missing, :old_method_missing) + send(:private, :method_missing) + send(:undef_method, :old_method_missing) end end Module.class_eval do - if method_defined?(:old_method_missing) - T.unsafe(self).alias_method :method_missing, :old_method_missing - T.unsafe(self).undef :old_method_missing + if method_defined?(:old_method_missing) || private_method_defined?(:old_method_missing) + send(:alias_method, :method_missing, :old_method_missing) + send(:private, :method_missing) + send(:undef_method, :old_method_missing) end end Resource.class_eval do - if method_defined?(:old_method_missing) - T.unsafe(self).alias_method :method_missing, :old_method_missing - T.unsafe(self).undef :old_method_missing + if method_defined?(:old_method_missing) || private_method_defined?(:old_method_missing) + send(:alias_method, :method_missing, :old_method_missing) + send(:private, :method_missing) + send(:undef_method, :old_method_missing) end end DependencyCollector.class_eval do - if method_defined?(:old_parse_symbol_spec) - T.unsafe(self).alias_method :parse_symbol_spec, :old_parse_symbol_spec - T.unsafe(self).undef :old_parse_symbol_spec + if method_defined?(:old_parse_symbol_spec) || private_method_defined?(:old_parse_symbol_spec) + send(:alias_method, :parse_symbol_spec, :old_parse_symbol_spec) + send(:private, :parse_symbol_spec) + send(:undef_method, :old_parse_symbol_spec) end end + DependencyCollector.clear_cache 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 93e50fa4c9c40..063d7e427650a 100644 --- a/Library/Homebrew/test/cask/artifact/shared_examples/uninstall_zap.rb +++ b/Library/Homebrew/test/cask/artifact/shared_examples/uninstall_zap.rb @@ -290,6 +290,7 @@ it "is supported" do allow(subject).to receive(:running_processes).with(bundle_id) .and_return(unix_pids.map { |pid| [pid, 0, bundle_id] }) + allow(subject).to receive(:sleep).with(3) signals.each do |signal| expect(Process).to receive(:kill).with(signal, *unix_pids).and_return(1) diff --git a/Library/Homebrew/test/cask/installer_spec.rb b/Library/Homebrew/test/cask/installer_spec.rb index c534e09123dae..5a781cb44b05a 100644 --- a/Library/Homebrew/test/cask/installer_spec.rb +++ b/Library/Homebrew/test/cask/installer_spec.rb @@ -4,6 +4,14 @@ RSpec.describe Cask::Installer, :cask do let(:klass) { Cask::Installer } + def stub_dmg_extraction + allow(UnpackStrategy::Dmg).to receive(:can_extract?).and_return(true) + allow_any_instance_of(UnpackStrategy::Dmg).to receive(:extract_nestedly) do |_strategy, to:, **| + to.mkpath + yield to + end + end + describe "install" do it "downloads and installs a nice fresh Cask" do caffeine = Cask::CaskLoader.load(cask_path("local-caffeine")) @@ -16,6 +24,7 @@ it "works with HFS+ dmg-based Casks" do asset = Cask::CaskLoader.load(cask_path("container-dmg")) + stub_dmg_extraction { |path| FileUtils.touch path/"container" } klass.new(asset).install @@ -165,6 +174,7 @@ it "installs a cask from a dmg file" do transmission = Cask::CaskLoader.load(cask_path("local-transmission")) + stub_dmg_extraction { |path| (path/"Transmission.app").mkpath } expect(transmission).not_to be_installed diff --git a/Library/Homebrew/test/cask/upgrade_spec.rb b/Library/Homebrew/test/cask/upgrade_spec.rb index d803425b98baa..76f1fc75e08be 100644 --- a/Library/Homebrew/test/cask/upgrade_spec.rb +++ b/Library/Homebrew/test/cask/upgrade_spec.rb @@ -615,19 +615,23 @@ def write_info_plist(path, short_version:, bundle_version:) end context "when there were multiple failures" do - # These tests perform actual upgrades and test error handling, - # so they need full real installations. + # This test exercises upgrade error handling, so it needs installed Casks. before do [ "outdated/bad-checksum", "outdated/local-transmission-zip", "outdated/bad-checksum2", ].each do |cask| - Cask::Installer.new(Cask::CaskLoader.load(cask_path(cask))).install + InstallHelper.stub_cask_installation(Cask::CaskLoader.load(cask_path(cask))) end + + bad_checksum_2_path = Cask::CaskLoader.load("bad-checksum2").config.appdir.join("container") + FileUtils.rm_rf(bad_checksum_2_path) + FileUtils.touch(bad_checksum_2_path) end it "does not end the upgrade process" do + upgraded_tokens = [] bad_checksum = Cask::CaskLoader.load("bad-checksum") bad_checksum_path = bad_checksum.config.appdir.join("Caffeine.app") @@ -646,10 +650,19 @@ def write_info_plist(path, short_version:, bundle_version:) expect(bad_checksum_2_path).to be_a_file expect(bad_checksum_2.installed_version).to eq "1.2.2" + allow(klass).to receive(:upgrade_cask) do |_, new_cask, **| + upgraded_tokens << new_cask.token + raise Cask::CaskError, "failed" if new_cask.token.start_with?("bad-checksum") + + InstallHelper.stub_cask_installation(new_cask) + end + expect do - klass.upgrade_casks!(args:) + klass.upgrade_casks!(args:, skip_prefetch: true) end.to raise_error(Cask::MultipleCaskErrors) + expect(upgraded_tokens).to contain_exactly("bad-checksum", "bad-checksum2", "local-transmission-zip") + expect(bad_checksum).to be_installed expect(bad_checksum_path).to be_a_directory expect(bad_checksum.installed_version).to eq "1.2.2" diff --git a/Library/Homebrew/test/cmd/--cache_spec.rb b/Library/Homebrew/test/cmd/--cache_spec.rb index ae5f72c5d3a88..ba95fac8fdee7 100644 --- a/Library/Homebrew/test/cmd/--cache_spec.rb +++ b/Library/Homebrew/test/cmd/--cache_spec.rb @@ -5,27 +5,20 @@ require "cmd/shared_examples/args_parse" RSpec.describe Homebrew::Cmd::Cache do + let(:klass) { Homebrew::Cmd::Cache } + it_behaves_like "parseable arguments" - it "prints all cache files for a given Formula", :integration_test do - expect { brew "--cache", testball } - .to output(%r{#{HOMEBREW_CACHE}/downloads/[\da-f]{64}--testball-}o).to_stdout - .and be_a_success - expect { brew "--cache", "--formula", testball } + it "prints all cache files for a given Formula" do + expect { klass.new(["--formula", (TEST_FIXTURE_DIR/"testball.rb").to_s]).run } .to output(%r{#{HOMEBREW_CACHE}/downloads/[\da-f]{64}--testball-}o).to_stdout .and not_to_output.to_stderr - .and be_a_success end - it "prints the cache files for a given Cask", :integration_test, :needs_macos do - expect { brew "--cache", cask_path("local-caffeine") } - .to output(%r{#{HOMEBREW_CACHE}/downloads/[\da-f]{64}--caffeine\.zip}o).to_stdout - .and output(/Treating #{Regexp.escape(cask_path("local-caffeine"))} as a cask/).to_stderr - .and be_a_success - expect { brew "--cache", "--cask", cask_path("local-caffeine") } + it "prints the cache files for a given Cask", :cask do + expect { klass.new(["--cask", cask_path("local-caffeine").to_s]).run } .to output(%r{#{HOMEBREW_CACHE}/downloads/[\da-f]{64}--caffeine\.zip}o).to_stdout .and not_to_output.to_stderr - .and be_a_success end it "prints the cache files for a given Formula and Cask", :integration_test, :needs_macos do diff --git a/Library/Homebrew/test/cmd/--caskroom_spec.rb b/Library/Homebrew/test/cmd/--caskroom_spec.rb index 571b7bcaad4ce..3f17798f6b387 100644 --- a/Library/Homebrew/test/cmd/--caskroom_spec.rb +++ b/Library/Homebrew/test/cmd/--caskroom_spec.rb @@ -5,6 +5,8 @@ require "cmd/shared_examples/args_parse" RSpec.describe Homebrew::Cmd::Caskroom do + let(:klass) { Homebrew::Cmd::Caskroom } + it_behaves_like "parseable arguments" it "prints Homebrew's Caskroom", :integration_test do @@ -14,11 +16,16 @@ .and be_a_success end - it "prints the Caskroom for Casks", :integration_test do - expect { brew "--caskroom", cask_path("local-transmission"), cask_path("local-caffeine") } + it "prints the Caskroom for Casks" do + cmd = klass.new(%w[local-transmission local-caffeine]) + allow(cmd.args.named).to receive(:to_casks).and_return([ + instance_double(Cask::Cask, token: "local-transmission"), + instance_double(Cask::Cask, token: "local-caffeine"), + ]) + + expect { cmd.run } .to output("#{HOMEBREW_PREFIX/"Caskroom"/"local-transmission"}\n" \ - "#{HOMEBREW_PREFIX/"Caskroom"/"local-caffeine\n"}").to_stdout + "#{HOMEBREW_PREFIX/"Caskroom"/"local-caffeine"}\n").to_stdout .and not_to_output.to_stderr - .and be_a_success end end diff --git a/Library/Homebrew/test/cmd/--cellar_spec.rb b/Library/Homebrew/test/cmd/--cellar_spec.rb index c9c59f85ecb68..9dd112a89a952 100644 --- a/Library/Homebrew/test/cmd/--cellar_spec.rb +++ b/Library/Homebrew/test/cmd/--cellar_spec.rb @@ -5,6 +5,8 @@ require "cmd/shared_examples/args_parse" RSpec.describe Homebrew::Cmd::Cellar do + let(:klass) { Homebrew::Cmd::Cellar } + it_behaves_like "parseable arguments" it "prints Homebrew's Cellar", :integration_test do @@ -14,10 +16,13 @@ .and be_a_success end - it "prints the Cellar for a Formula", :integration_test do - expect { brew "--cellar", testball } + it "prints the Cellar for a Formula" do + cmd = klass.new(["testball"]) + allow(cmd.args.named).to receive(:to_resolved_formulae) + .and_return([instance_double(Formula, rack: HOMEBREW_CELLAR/"testball")]) + + expect { cmd.run } .to output(%r{#{HOMEBREW_CELLAR}/testball}o).to_stdout .and not_to_output.to_stderr - .and be_a_success end end diff --git a/Library/Homebrew/test/cmd/--prefix_spec.rb b/Library/Homebrew/test/cmd/--prefix_spec.rb index 09c0a568065b4..37cc1518c5370 100644 --- a/Library/Homebrew/test/cmd/--prefix_spec.rb +++ b/Library/Homebrew/test/cmd/--prefix_spec.rb @@ -5,6 +5,8 @@ require "cmd/shared_examples/args_parse" RSpec.describe Homebrew::Cmd::Prefix do + let(:klass) { Homebrew::Cmd::Prefix } + it_behaves_like "parseable arguments" it "prints Homebrew's prefix", :integration_test do @@ -14,24 +16,31 @@ .and be_a_success end - it "prints the prefix for a Formula", :integration_test, :needs_homebrew_core do - expect { brew_sh "--prefix", "wget" } - .to output("#{ENV.fetch("HOMEBREW_PREFIX")}/opt/wget\n").to_stdout + it "prints the prefix for a Formula" do + cmd = klass.new(["testball"]) + allow(cmd.args.named).to receive(:to_resolved_formulae) + .and_return([instance_double(Formula, opt_prefix: HOMEBREW_PREFIX/"opt/testball")]) + + expect { cmd.run } + .to output("#{HOMEBREW_PREFIX}/opt/testball\n").to_stdout .and not_to_output.to_stderr - .and be_a_success end - it "errors if the given Formula doesn't exist", :integration_test do - expect { brew "--prefix", "nonexistent" } - .to output(/No available formula/).to_stderr - .and not_to_output.to_stdout - .and be_a_failure + it "errors if the given Formula doesn't exist" do + cmd = klass.new(["nonexistent"]) + allow(cmd.args.named).to receive(:to_resolved_formulae) + .and_raise(FormulaUnavailableError.new("nonexistent")) + + expect { cmd.run }.to raise_error(FormulaUnavailableError, /nonexistent/) end - it "prints a warning when `--installed` is used and the given Formula is not installed", :integration_test do - expect { brew "--prefix", "--installed", testball } - .to not_to_output.to_stdout - .and output(/testball/).to_stderr - .and be_a_failure + it "prints a warning when `--installed` is used and the given Formula is not installed" do + cmd = klass.new(["--installed", "testball"]) + allow(cmd.args.named).to receive(:to_resolved_formulae).and_return([ + instance_double(Formula, name: "testball", opt_prefix: HOMEBREW_PREFIX/"opt/testball", optlinked?: false), + ]) + + expect { cmd.run } + .to raise_error(NotAKegError, /testball/) end end diff --git a/Library/Homebrew/test/cmd/--repository_spec.rb b/Library/Homebrew/test/cmd/--repository_spec.rb index 7c11823e75db4..a3e8eeb18acc1 100644 --- a/Library/Homebrew/test/cmd/--repository_spec.rb +++ b/Library/Homebrew/test/cmd/--repository_spec.rb @@ -1,25 +1,29 @@ # typed: false # frozen_string_literal: true -RSpec.describe "brew --repository", type: :system do - it "prints Homebrew's repository", :integration_test do - expect { brew_sh "--repository" } - .to output("#{ENV.fetch("HOMEBREW_REPOSITORY")}\n").to_stdout - .and not_to_output.to_stderr - .and be_a_success - end +require "open3" - it "prints a Tap's repository", :integration_test do - expect { brew_sh "--repository", "foo/bar" } - .to output("#{ENV.fetch("HOMEBREW_LIBRARY")}/Taps/foo/homebrew-bar\n").to_stdout - .and not_to_output.to_stderr - .and be_a_success - end +RSpec.describe "brew --repository", type: :system do + it "prints Homebrew and Tap repositories" do + stdout, stderr, status = Open3.capture3( + { + "HOMEBREW_LIBRARY" => ENV.fetch("HOMEBREW_LIBRARY"), + "HOMEBREW_REPOSITORY" => ENV.fetch("HOMEBREW_REPOSITORY"), + }, + "/bin/bash", "-c", <<~SH, + source "$1" + homebrew---repository + homebrew---repository foo/bar foo/homebrew-bar + SH + "bash", (HOMEBREW_LIBRARY_PATH/"cmd/--repository.sh").to_s + ) - it "prints a Tap's repository correctly when homebrew- prefix is supplied", :integration_test do - expect { brew_sh "--repository", "foo/homebrew-bar" } - .to output("#{ENV.fetch("HOMEBREW_LIBRARY")}/Taps/foo/homebrew-bar\n").to_stdout - .and not_to_output.to_stderr - .and be_a_success + expect(status).to be_success + expect(stdout).to eq(<<~EOS) + #{ENV.fetch("HOMEBREW_REPOSITORY")} + #{ENV.fetch("HOMEBREW_LIBRARY")}/Taps/foo/homebrew-bar + #{ENV.fetch("HOMEBREW_LIBRARY")}/Taps/foo/homebrew-bar + EOS + expect(stderr).to be_empty end end diff --git a/Library/Homebrew/test/cmd/desc_spec.rb b/Library/Homebrew/test/cmd/desc_spec.rb index c500f8a7f9d1a..dbaf73fdad15b 100644 --- a/Library/Homebrew/test/cmd/desc_spec.rb +++ b/Library/Homebrew/test/cmd/desc_spec.rb @@ -55,25 +55,26 @@ .and not_to_output.to_stderr end - it "errors when searching without --eval-all", :integration_test, :no_api do - setup_test_formula "testball" - - expect { brew "desc", "--search", "testball" } - .to output(/`brew desc --search` needs `--eval-all` passed or `HOMEBREW_EVAL_ALL=1` set!/).to_stderr - .and be_a_failure + it "errors when searching without --eval-all" do + with_env("HOMEBREW_NO_INSTALL_FROM_API" => "1") do + expect { klass.new(["--search", "testball"]).run } + .to raise_error(UsageError, /`brew desc --search` needs `--eval-all` passed/) + end end - it "successfully searches with --search --eval-all", :integration_test, :no_api do - setup_test_formula "testball" + it "successfully searches with --search --eval-all" do + expect(Homebrew::Search).to receive(:search_descriptions) + .with("ball", anything, search_type: Descriptions::SearchField::Either) - expect { brew "desc", "--search", "--eval-all", "ball" } - .to output(/testball: Some test/).to_stdout - .and not_to_output.to_stderr + expect { klass.new(["--search", "--eval-all", "ball"]).run } + .to not_to_output.to_stderr end - it "successfully searches without --eval-all, with API", :integration_test, :needs_network do - setup_test_formula "testball" + it "successfully searches without --eval-all, with API" do + expect(Homebrew::Search).to receive(:search_descriptions) + .with("testball", anything, search_type: Descriptions::SearchField::Either) - expect { brew "desc", "--search", "testball" }.to be_a_success + expect { klass.new(["--search", "testball"]).run } + .to not_to_output.to_stderr end end diff --git a/Library/Homebrew/test/cmd/fetch_spec.rb b/Library/Homebrew/test/cmd/fetch_spec.rb index 940f47156ddd6..1fa07690ba9c6 100644 --- a/Library/Homebrew/test/cmd/fetch_spec.rb +++ b/Library/Homebrew/test/cmd/fetch_spec.rb @@ -7,24 +7,16 @@ RSpec.describe Homebrew::Cmd::FetchCmd do it_behaves_like "parseable arguments" - it "downloads the Formula's URL", :integration_test do - setup_test_formula "testball" - - expect { brew "fetch", "testball" }.to be_a_success - - expect(HOMEBREW_CACHE/"testball--0.1.tbz").to be_a_symlink - expect(HOMEBREW_CACHE/"testball--0.1.tbz").to exist - end - - it "concurrently downloads formula URLs", :integration_test do + it "downloads Formula and Cask URLs concurrently", :cask, :integration_test do setup_test_formula "testball1" setup_test_formula "testball2" - expect { brew "fetch", "testball1", "testball2", "HOMEBREW_DOWNLOAD_CONCURRENCY" => "2" }.to be_a_success + expect { brew "fetch", "testball1", "testball2", "local-caffeine" }.to be_a_success expect(HOMEBREW_CACHE/"testball1--0.1.tbz").to be_a_symlink expect(HOMEBREW_CACHE/"testball1--0.1.tbz").to exist expect(HOMEBREW_CACHE/"testball2--0.1.tbz").to be_a_symlink expect(HOMEBREW_CACHE/"testball2--0.1.tbz").to exist + expect((HOMEBREW_CACHE/"downloads").glob("*--caffeine.zip")).not_to be_empty end end diff --git a/Library/Homebrew/test/cmd/home_spec.rb b/Library/Homebrew/test/cmd/home_spec.rb index c39d8e95ff306..11edfa318eac5 100644 --- a/Library/Homebrew/test/cmd/home_spec.rb +++ b/Library/Homebrew/test/cmd/home_spec.rb @@ -5,8 +5,14 @@ require "cmd/shared_examples/args_parse" RSpec.describe Homebrew::Cmd::Home do + let(:testballhome) do + formula("testballhome") do + homepage "https://brew.sh/testballhome" + url "https://brew.sh/testballhome-1.0" + end + end let(:testballhome_homepage) do - Formula["testballhome"].homepage + testballhome.homepage end let(:local_caffeine_path) do @@ -26,32 +32,38 @@ .and be_a_success end - it "opens the homepage for a given Formula", :integration_test do - setup_test_formula "testballhome" + it "opens the homepage for a given Formula" do + stub_formula_loader testballhome, call_original: true + cmd = Homebrew::Cmd::Home.new(["testballhome"]) + expect(cmd).to receive(:exec_browser).with(testballhome_homepage) - expect { brew "home", "testballhome", "HOMEBREW_BROWSER" => "echo" } - .to output(/#{testballhome_homepage}/).to_stdout + expect { cmd.run } + .to output(/Opening homepage for Formula testballhome/).to_stdout .and not_to_output.to_stderr - .and be_a_success end - it "opens the homepage for a given Cask", :integration_test, :needs_macos do - expect { brew "home", local_caffeine_path, "HOMEBREW_BROWSER" => "echo" } - .to output(/#{local_caffeine_homepage}/).to_stdout + it "opens the homepage for a given Cask", :cask, :needs_macos do + cmd = Homebrew::Cmd::Home.new([local_caffeine_path.to_s]) + expect(cmd).to receive(:exec_browser).with(local_caffeine_homepage) + + expect { cmd.run } + .to output(/Opening homepage for Cask local-caffeine/).to_stdout .and output(/Treating #{Regexp.escape(local_caffeine_path)} as a cask/).to_stderr - .and be_a_success - expect { brew "home", "--cask", local_caffeine_path, "HOMEBREW_BROWSER" => "echo" } - .to output(/#{local_caffeine_homepage}/).to_stdout + cmd = Homebrew::Cmd::Home.new(["--cask", local_caffeine_path.to_s]) + expect(cmd).to receive(:exec_browser).with(local_caffeine_homepage) + + expect { cmd.run } + .to output(/Opening homepage for Cask local-caffeine/).to_stdout .and not_to_output.to_stderr - .and be_a_success end - it "opens the homepages for a given formula and Cask", :integration_test, :needs_macos do - setup_test_formula "testballhome" + it "opens the homepages for a given formula and Cask", :cask, :needs_macos do + stub_formula_loader testballhome, call_original: true + cmd = Homebrew::Cmd::Home.new(["testballhome", local_caffeine_path.to_s]) + expect(cmd).to receive(:exec_browser).with(testballhome_homepage, local_caffeine_homepage) - expect { brew "home", "testballhome", local_caffeine_path, "HOMEBREW_BROWSER" => "echo" } - .to output(/#{testballhome_homepage} #{local_caffeine_homepage}/).to_stdout + expect { cmd.run } + .to output(/Opening homepage for Formula testballhome.*Opening homepage for Cask local-caffeine/m).to_stdout .and output(/Treating #{Regexp.escape(local_caffeine_path)} as a cask/).to_stderr - .and be_a_success end end diff --git a/Library/Homebrew/test/cmd/info_spec.rb b/Library/Homebrew/test/cmd/info_spec.rb index c5b6bb53fc6fe..d07c97f05ef99 100644 --- a/Library/Homebrew/test/cmd/info_spec.rb +++ b/Library/Homebrew/test/cmd/info_spec.rb @@ -38,13 +38,17 @@ def installed_info_cask it_behaves_like "parseable arguments" - it "prints as json with the --json=v1 flag", :integration_test do - setup_test_formula "testball" + it "prints as json with the --json=v1 flag" do + test_formula = formula("testball") do + url "https://brew.sh/testball-0.1.tar.gz" + desc "Some test" + end + info = klass.new(["--json=v1", "testball"]) + allow(info.args.named).to receive(:to_formulae).and_return([test_formula]) - expect { brew "info", "testball", "--json=v1" } + expect { info.run } .to output(a_json_string).to_stdout .and not_to_output.to_stderr - .and be_a_success end it "prints as json with the --json=v2 flag", :integration_test do @@ -213,11 +217,15 @@ def installed_info_cask .and not_to_output.to_stderr end - it "uses slim formula information when quiet is passed", :integration_test do - setup_test_formula "testball" + it "uses slim formula information when quiet is passed" do + test_formula = formula("testball") do + url "https://brew.sh/testball-0.1.tar.gz" + desc "Some test" + end info = klass.new(["--quiet", "testball"]) + allow(info.args.named).to receive(:to_formulae_and_casks_and_unavailable).and_return([test_formula]) - expect(info).to receive(:info_formula_summary).with(kind_of(Formula)) + expect(info).to receive(:info_formula_summary).with(test_formula) expect { info.run } .to not_to_output.to_stderr end @@ -853,11 +861,12 @@ def installed_info_cask url "https://brew.sh/testball-0.1.tar.gz" homepage "https://brew.sh/testball" desc "Some test" - - depends_on :linux end allow(info).to receive(:github_info).with(formula).and_return("https://example.com/testball.rb") - allow(formula).to receive(:core_formula?).and_return(false) + allow(formula).to receive_messages( + core_formula?: false, + requirements: Requirements.new(LinuxRequirement.new), + ) expect { info.send(:info_formula, formula) } .to output(/Requirements\nRequired: .*Linux/).to_stdout @@ -873,15 +882,13 @@ def installed_info_cask url "https://brew.sh/testball-0.1.tar.gz" homepage "https://brew.sh/testball" desc "Some test" - - if OS.mac? - depends_on :linux - else - depends_on macos: :sonoma - end end + os_requirement = OS.mac? ? LinuxRequirement.new : MacOSRequirement.new([:sonoma]) allow(info).to receive(:github_info).with(formula).and_return("https://example.com/testball.rb") - allow(formula).to receive(:core_formula?).and_return(false) + allow(formula).to receive_messages( + core_formula?: false, + requirements: Requirements.new(os_requirement), + ) expect { info.send(:info_formula, formula) } .to not_to_output(/Installs from source: yes/).to_stdout diff --git a/Library/Homebrew/test/cmd/install_spec.rb b/Library/Homebrew/test/cmd/install_spec.rb index 5f720686e9d57..07abf756043ea 100644 --- a/Library/Homebrew/test/cmd/install_spec.rb +++ b/Library/Homebrew/test/cmd/install_spec.rb @@ -230,86 +230,39 @@ expect(Homebrew).to have_failed end - context "when using a bottle" do - let(:formula_name) { "testball_bottle" } - let(:formula_prefix) { HOMEBREW_CELLAR/formula_name/"0.1" } - let(:formula_prefix_regex) { /#{Regexp.escape(formula_prefix)}/o } - let(:option_file) { formula_prefix/"foo/test" } - let(:bottle_file) { formula_prefix/"bin/helloworld" } - - it "installs a Formula", :integration_test do - setup_test_formula formula_name - - expect { brew "install", formula_name } - .to output(formula_prefix_regex).to_stdout - .and output(/✔︎.*/m).to_stderr - .and be_a_success - expect(option_file).not_to be_a_file - expect(bottle_file).to be_a_file - - uninstall_test_formula formula_name - - expect { brew "install", "--ask", formula_name } - .to output(/.*Would install 1 formula:\s*#{formula_name}.*/).to_stdout - .and output(/✔︎.*/m).to_stderr - .and be_a_success - expect(option_file).not_to be_a_file - expect(bottle_file).to be_a_file + context "when installing Formulae" do + it "builds from source and pours a keg-only bottle", :integration_test do + source_formula_name = "sourceball" + source_formula_prefix = HOMEBREW_CELLAR/source_formula_name/"0.1" + bottle_formula_name = "testball_bottle" + bottle_formula_prefix = HOMEBREW_CELLAR/bottle_formula_name/"0.1" - uninstall_test_formula formula_name + setup_test_formula source_formula_name, <<~RUBY + url "file://#{TEST_FIXTURE_DIR}/tarballs/testball-0.1.tbz" + sha256 TESTBALL_SHA256 - expect { brew "install", formula_name, { "HOMEBREW_FORBIDDEN_FORMULAE" => formula_name } } - .to not_to_output(formula_prefix_regex).to_stdout - .and output(/#{formula_name} was forbidden/).to_stderr - .and be_a_failure - expect(formula_prefix).not_to exist - end - - it "installs a keg-only Formula", :integration_test do - setup_test_formula formula_name, <<~RUBY + def install + (prefix/"built-from-source").write("test") + end + RUBY + setup_test_formula bottle_formula_name, <<~RUBY keg_only "test reason" RUBY - expect { brew "install", formula_name } - .to output(formula_prefix_regex).to_stdout + expect { brew "install", source_formula_name, bottle_formula_name } + .to output(/#{Regexp.escape(source_formula_prefix)}.*#{Regexp.escape(bottle_formula_prefix)}/m).to_stdout .and output(/✔︎.*/m).to_stderr .and be_a_success - expect(option_file).not_to be_a_file - expect(bottle_file).to be_a_file + expect(source_formula_prefix/"built-from-source").to be_a_file + expect(bottle_formula_prefix/"foo/test").not_to be_a_file + expect(bottle_formula_prefix/"bin/helloworld").to be_a_file expect(HOMEBREW_PREFIX/"bin/helloworld").not_to be_a_file end end - context "when building from source" do + context "when installing HEAD" do let(:formula_name) { "testball1" } - it "installs a Formula", :integration_test do - formula_prefix = HOMEBREW_CELLAR/formula_name/"0.1" - formula_prefix_regex = /#{Regexp.escape(formula_prefix)}/o - option_file = formula_prefix/"foo/test" - always_built_file = formula_prefix/"bin/test" - - setup_test_formula formula_name - - expect { brew "install", formula_name, "--with-foo" } - .to output(formula_prefix_regex).to_stdout - .and output(/✔︎.*/m).to_stderr - .and be_a_success - expect(option_file).to be_a_file - expect(always_built_file).to be_a_file - - uninstall_test_formula formula_name - - expect { brew "install", formula_name, "--debug-symbols", "--build-from-source" } - .to output(formula_prefix_regex).to_stdout - .and output(/✔︎.*/m).to_stderr - .and be_a_success - expect(option_file).not_to be_a_file - expect(always_built_file).to be_a_file - expect(formula_prefix/"bin/test.dSYM/Contents/Resources/DWARF/test").to be_a_file if OS.mac? - expect(HOMEBREW_CACHE/"Sources/#{formula_name}").to be_a_directory - end - it "installs a HEAD Formula", :integration_test do testball1_prefix = HOMEBREW_CELLAR/"testball1/HEAD-d5eb689" repo_path = HOMEBREW_CACHE/"repo" diff --git a/Library/Homebrew/test/cmd/leaves_spec.rb b/Library/Homebrew/test/cmd/leaves_spec.rb index 4a2ac3bb80ad5..930e280ac9307 100644 --- a/Library/Homebrew/test/cmd/leaves_spec.rb +++ b/Library/Homebrew/test/cmd/leaves_spec.rb @@ -5,34 +5,42 @@ require "cmd/shared_examples/args_parse" RSpec.describe Homebrew::Cmd::Leaves do + let(:klass) { Homebrew::Cmd::Leaves } + it_behaves_like "parseable arguments" - context "when there are no installed Formulae", :integration_test do + context "when there are no installed Formulae" do it "prints nothing" do - setup_test_formula "foo" - setup_test_formula "bar" + allow(Formula).to receive(:installed).and_return([]) + allow(Cask::Caskroom).to receive(:casks).and_return([]) - expect { brew "leaves" } + expect { klass.new([]).run } .to not_to_output.to_stdout .and not_to_output.to_stderr - .and be_a_success end end - context "when there are only installed Formulae without dependencies", :integration_test do + context "when there are only installed Formulae without dependencies" do it "prints all installed Formulae" do - setup_test_formula "foo", tab_attributes: { installed_on_request: true } - setup_test_formula "bar" + allow(Formula).to receive(:installed).and_return([ + instance_double( + Formula, + any_installed_keg: nil, + full_name: "foo", + installed_runtime_formula_dependencies: [], + possible_names: ["foo"], + ), + ]) + allow(Cask::Caskroom).to receive(:casks).and_return([]) - expect { brew "leaves" } + expect { klass.new([]).run } .to output("foo\n").to_stdout .and not_to_output.to_stderr - .and be_a_success end end - context "when there are installed Formulae", :integration_test, :no_api do - it "prints all installed Formulae that are not dependencies of another installed Formula" do + context "when there are installed Formulae", :no_api do + it "prints all installed Formulae that are not dependencies of another installed Formula", :integration_test do setup_test_formula "foo" setup_test_formula "bar" (HOMEBREW_CELLAR/"foo/0.1/somedir").mkpath @@ -48,17 +56,30 @@ # Simulate: "foo" was renamed to "newname"; "bar" depends on it but its tab # still records the old dependency name under a tap-qualified full_name # (not yet regenerated after rename). Also exercises the tap-prefix strip path. - setup_test_formula "newname" - setup_test_formula "bar", tab_attributes: { runtime_dependencies: [{ "full_name" => "homebrew/core/foo" }] } - (HOMEBREW_CELLAR/"newname/1.0/somedir").mkpath - - CoreTap.instance.path.join("formula_renames.json").write('{"foo":"newname"}') - CoreTap.instance.clear_cache + allow(Formula).to receive(:installed).and_return([ + instance_double( + Formula, + any_installed_keg: nil, + full_name: "newname", + installed_runtime_formula_dependencies: [], + possible_names: %w[newname foo], + ), + instance_double( + Formula, + any_installed_keg: instance_double( + Keg, + runtime_dependencies: [{ "full_name" => "homebrew/core/foo" }], + ), + full_name: "bar", + installed_runtime_formula_dependencies: [], + possible_names: ["bar"], + ), + ]) + allow(Cask::Caskroom).to receive(:casks).and_return([]) - expect { brew "leaves" } + expect { klass.new([]).run } .to output("bar\n").to_stdout .and not_to_output.to_stderr - .and be_a_success end end end diff --git a/Library/Homebrew/test/cmd/link_spec.rb b/Library/Homebrew/test/cmd/link_spec.rb index 83be179e1ccbc..bd2d1cc6004c3 100644 --- a/Library/Homebrew/test/cmd/link_spec.rb +++ b/Library/Homebrew/test/cmd/link_spec.rb @@ -43,31 +43,38 @@ "@-versioned" => "testball-link-output@1.0", "-full" => "testball-link-output-full", }.each do |formula_type, formula_name| - it "does not print keg-only output when linking a #{formula_type} formula", :integration_test do - formula_content = <<~RUBY + it "does not print keg-only output when linking a #{formula_type} formula" do + test_formula = formula(formula_name) do + url "https://brew.sh/#{formula_name}-1.0" keg_only :versioned_formula def caveats "unexpected caveat output" end - def post_install - puts "unexpected post_install output" - end - RUBY + def post_install; end + end + keg = instance_double( + Keg, + rack: HOMEBREW_CELLAR/formula_name, + linked?: false, + name: formula_name, + to_formula: test_formula, + to_s: "#{formula_name}/1.0", + ) + cmd = klass.new([formula_name]) - setup_test_formula formula_name, formula_content, tab_attributes: { installed_on_request: true } - Formula[formula_name].bin.mkpath - FileUtils.touch Formula[formula_name].bin/"link-output-test" - Formula[formula_name].any_installed_keg.unlink + allow(cmd.args.named).to receive(:to_latest_kegs).and_return([keg]) + allow(Formulary).to receive(:keg_only?).with(keg.rack).and_return(true) + allow(Homebrew::Unlink).to receive(:unlink_link_overwrite_formulae) + allow(keg).to receive(:lock).and_yield + allow(keg).to receive(:link).and_return(1) unexpected_output = /unexpected caveat output|unexpected post_install output| If you need to have this software first in your PATH|keg-only/x - expect { brew "link", formula_name } + expect { cmd.run } .to not_to_output(unexpected_output).to_stdout .and not_to_output.to_stderr - .and be_a_success - expect(HOMEBREW_PREFIX/"bin/link-output-test").to be_a_file end end end diff --git a/Library/Homebrew/test/cmd/list_spec.rb b/Library/Homebrew/test/cmd/list_spec.rb index 3757efe8bd3f9..1af9513e715c9 100644 --- a/Library/Homebrew/test/cmd/list_spec.rb +++ b/Library/Homebrew/test/cmd/list_spec.rb @@ -1,10 +1,13 @@ # typed: false # frozen_string_literal: true +require "open3" + require "cmd/list" require "cmd/shared_examples/args_parse" RSpec.describe Homebrew::Cmd::List do + let(:klass) { Homebrew::Cmd::List } let(:formulae) { %w[bar foo qux] } def list_versions_json(formulae: [], casks: []) @@ -31,8 +34,7 @@ def install_cask(token) Cask::CaskLoader.load(token).tap { |cask| InstallHelper.stub_cask_installation(cask) } end - def brew_sh_list(*args) - env = args.last.is_a?(Hash) ? args.pop : {} + def run_list_bash(env = {}) stdout, stderr, status = Open3.capture3( { "HOMEBREW_CASKROOM" => Cask::Caskroom.path.to_s, @@ -40,8 +42,69 @@ def brew_sh_list(*args) "HOMEBREW_LIBRARY" => HOMEBREW_LIBRARY_PATH.to_s, "HOMEBREW_PREFIX" => HOMEBREW_PREFIX.to_s, }.merge(env), - "/bin/bash", "-c", 'source "$1"; shift; homebrew-list "$@"', - "bash", (HOMEBREW_LIBRARY_PATH/"list.sh").to_s, "list", *args + "/bin/bash", "-c", <<~SH, + source "$1" + + stdout_file="$(mktemp)" + stderr_file="$(mktemp)" + trap 'rm -f "${stdout_file}" "${stderr_file}"' EXIT + + check() { + local label="$1" + local expected_status="$2" + local expected_stdout="$3" + local expected_stderr="$4" + shift 4 + + ( "$@" ) >"${stdout_file}" 2>"${stderr_file}" + status="$?" + if [[ "${status}" -ne "${expected_status}" ]] + then + echo "${label}: expected status ${expected_status}, got ${status}" >&2 + return 1 + fi + if ! diff -u <(printf '%s' "${expected_stdout}") "${stdout_file}" >&2 + then + echo "${label}: stdout mismatch" >&2 + return 1 + fi + if ! diff -u <(printf '%s' "${expected_stderr}") "${stderr_file}" >&2 + then + echo "${label}: stderr mismatch" >&2 + return 1 + fi + } + + empty_versions_json() { + HOMEBREW_CELLAR="${EMPTY_CELLAR}" HOMEBREW_CASKROOM="${EMPTY_CASKROOM}" \\ + homebrew-list list --versions --json + } + + missing_jq_versions_json() { + PATH="${NO_JQ_PATH}" HOMEBREW_PATH="${NO_JQ_PATH}" HOMEBREW_PREFIX="${NO_JQ_PREFIX}" \\ + HOMEBREW_CELLAR="${NO_JQ_CELLAR}" HOMEBREW_CASKROOM="${NO_JQ_CASKROOM}" \\ + homebrew-list list --versions --json + } + + check "formulae and casks" 0 "${EXPECTED_PLAIN}" "" homebrew-list list + check "formula and cask versions JSON" 0 "${EXPECTED_JSON}" "" homebrew-list list --versions --json + check "formula versions JSON" 0 "${EXPECTED_FORMULA_JSON}" "" \\ + homebrew-list list --versions --json --formula + check "cask versions JSON" 0 "${EXPECTED_CASK_JSON}" "" homebrew-list list --versions --json --cask + check "empty versions JSON" 0 "${EXPECTED_EMPTY_JSON}" "" empty_versions_json + check "JSON without versions" 1 "" \\ + $'Error: `brew list --json` requires `--versions`.\\n' \\ + homebrew-list list --json + check "JSON with ls flags" 1 "" \\ + $'Error: `brew list --versions --json` cannot be combined with `-1`, `-l`, `-r` or `-t`.\\n' \\ + homebrew-list list --versions --json -1 + check "JSON with formula and cask filters" 1 "" \\ + $'Error: `--formula` and `--cask` are mutually exclusive.\\n' \\ + homebrew-list list --versions --json --formula --cask + check "missing jq" 1 "" $'Error: jq is required for brew list --versions --json.\\n' \\ + missing_jq_versions_json + SH + "bash", (HOMEBREW_LIBRARY_PATH/"list.sh").to_s ) $stdout.print stdout $stderr.print stderr @@ -50,90 +113,17 @@ def brew_sh_list(*args) it_behaves_like "parseable arguments" - it "prints all installed formulae", :integration_test do + it "prints all installed formulae" do formulae.each do |f| - (HOMEBREW_CELLAR/f/"1.0/somedir").mkpath + install_formula_version f, "1.0" end - expect { brew "list", "--formula" } + expect { klass.new(["--formula"]).run } .to output("#{formulae.join("\n")}\n").to_stdout .and not_to_output.to_stderr - .and be_a_success - end - - it "prints all installed formulae and casks", :integration_test do - expect { brew_sh "list" } - .to be_a_success - .and not_to_output.to_stderr - end - - it "prints installed formulae and casks with versions as JSON", :cask, :integration_test do - install_formula_version "testball", "0.1" - install_cask "local-caffeine" - - expect { brew_sh_list "--versions", "--json" } - .to output(list_versions_json( - formulae: [{ name: "testball", versions: ["0.1"] }], - casks: [{ token: "local-caffeine", versions: ["1.2.3"] }], - )).to_stdout - .and not_to_output.to_stderr - .and be_a_success - end - - it "prints only installed formulae with versions as JSON", :cask, :integration_test do - install_formula_version "testball", "0.1" - install_cask "local-caffeine" - - expect { brew_sh_list "--versions", "--json", "--formula" } - .to output(list_versions_json( - formulae: [{ name: "testball", versions: ["0.1"] }], - )).to_stdout - .and not_to_output.to_stderr - .and be_a_success - end - - it "prints only installed casks with versions as JSON", :cask, :integration_test do - install_formula_version "testball", "0.1" - install_cask "local-caffeine" - - expect { brew_sh_list "--versions", "--json", "--cask" } - .to output(list_versions_json( - casks: [{ token: "local-caffeine", versions: ["1.2.3"] }], - )).to_stdout - .and not_to_output.to_stderr - .and be_a_success - end - - it "prints empty versions as JSON", :integration_test do - expect { brew_sh_list "--versions", "--json" } - .to output(list_versions_json).to_stdout - .and not_to_output.to_stderr - .and be_a_success - end - - it "fails when JSON is requested without versions", :integration_test do - expect { brew_sh_list "--json" } - .to output("Error: `brew list --json` requires `--versions`.\n").to_stderr - .and not_to_output.to_stdout - .and be_a_failure end - it "fails when JSON is requested with ls flags", :integration_test do - expect { brew_sh_list "--versions", "--json", "-1" } - .to output("Error: `brew list --versions --json` cannot be combined with `-1`, `-l`, `-r` or `-t`.\n") - .to_stderr - .and not_to_output.to_stdout - .and be_a_failure - end - - it "fails when JSON is requested for formulae and casks together", :integration_test do - expect { brew_sh_list "--versions", "--json", "--formula", "--cask" } - .to output("Error: `--formula` and `--cask` are mutually exclusive.\n").to_stderr - .and not_to_output.to_stdout - .and be_a_failure - end - - it "prints linked, opt-linked and pinned versions as JSON", :cask, :integration_test do + it "covers Bash list output and errors", :cask do install_formula_version "testball", "0.1" install_formula_version "testball", "0.2" (HOMEBREW_PREFIX/"var/homebrew/linked").mkpath @@ -148,52 +138,46 @@ def brew_sh_list(*args) FileUtils.ln_s Cask::Caskroom.path/"local-caffeine/1.2.3", HOMEBREW_PREFIX/"var/homebrew/pinned_casks/local-caffeine" - expect { brew_sh_list "--versions", "--json" } - .to output(list_versions_json( - formulae: [ - { name: "testball", versions: ["0.1", "0.2"], linked_version: "0.1", - optlinked_version: "0.2", pinned_version: "0.2" }, - ], - casks: [ - { token: "local-caffeine", versions: ["1.2.3"], pinned_version: "1.2.3" }, - ], - )).to_stdout - .and not_to_output.to_stderr - .and be_a_success - end - - it "fails when jq is unavailable for versions JSON", :integration_test do - mktmpdir do |dir| - stdout, stderr, status = Open3.capture3( - { - "HOMEBREW_CELLAR" => (dir/"Cellar").to_s, - "HOMEBREW_CASKROOM" => (dir/"Caskroom").to_s, - "HOMEBREW_PATH" => dir.to_s, - "HOMEBREW_PREFIX" => (dir/"prefix").to_s, - "PATH" => dir.to_s, - }, - "/bin/bash", "-c", 'source "$1"; shift; homebrew-list "$@"', - "bash", (HOMEBREW_LIBRARY_PATH/"list.sh").to_s, "list", "--versions", "--json" - ) - - expect(status).to be_a_failure - expect(stdout).to eq("") - expect(stderr).to eq("Error: jq is required for brew list --versions --json.\n") + empty_cellar = mktmpdir + empty_caskroom = mktmpdir + no_jq_root = mktmpdir + no_jq_cellar = no_jq_root/"Cellar" + no_jq_caskroom = no_jq_root/"Caskroom" + no_jq_prefix = no_jq_root/"prefix" + no_jq_cellar.mkpath + no_jq_caskroom.mkpath + no_jq_prefix.mkpath + formulae_json = [{ name: "testball", versions: ["0.1", "0.2"], linked_version: "0.1", + optlinked_version: "0.2", pinned_version: "0.2" }] + casks_json = [{ token: "local-caffeine", versions: ["1.2.3"], pinned_version: "1.2.3" }] + + expect do + expect(run_list_bash( + "EMPTY_CASKROOM" => empty_caskroom.to_s, + "EMPTY_CELLAR" => empty_cellar.to_s, + "EXPECTED_CASK_JSON" => list_versions_json(casks: casks_json), + "EXPECTED_EMPTY_JSON" => list_versions_json, + "EXPECTED_FORMULA_JSON" => list_versions_json(formulae: formulae_json), + "EXPECTED_JSON" => list_versions_json(formulae: formulae_json, casks: casks_json), + "EXPECTED_PLAIN" => "testball\nlocal-caffeine\n", + "NO_JQ_CASKROOM" => no_jq_caskroom.to_s, + "NO_JQ_CELLAR" => no_jq_cellar.to_s, + "NO_JQ_PATH" => no_jq_root.to_s, + "NO_JQ_PREFIX" => no_jq_prefix.to_s, + )).to be_success end + .to not_to_output.to_stdout + .and not_to_output.to_stderr end - it "fails when versions JSON reaches the Ruby fallback", :integration_test do - expect { brew "list", "--versions", "--json" } - .to output(/`brew list --versions --json` is only supported by the fast Bash path with `jq`\./).to_stderr - .and not_to_output.to_stdout - .and be_a_failure + it "fails when versions JSON reaches the Ruby fallback" do + expect { klass.new(["--versions", "--json"]).run } + .to raise_error(UsageError, /`brew list --versions --json` is only supported by the fast Bash path with `jq`\./) end - it "fails clearly when JSON without versions reaches the Ruby fallback", :integration_test do - expect { brew "list", "--json" } - .to output(/`brew list --json` requires `--versions`\./).to_stderr - .and not_to_output.to_stdout - .and be_a_failure + it "fails clearly when JSON without versions reaches the Ruby fallback" do + expect { klass.new(["--json"]).run } + .to raise_error(UsageError, /`brew list --json` requires `--versions`\./) end it "prints pinned formulae and casks", :cask, :integration_test do @@ -210,35 +194,34 @@ def brew_sh_list(*args) cask.unpin end - it "fails only for explicitly named missing pinned packages", :cask, :integration_test do - setup_test_formula "testball", tab_attributes: { installed_on_request: true } - Formula["testball"].pin + it "fails only for explicitly named missing pinned packages", :cask do + install_formula_version "testball", "0.1" + (HOMEBREW_PREFIX/"var/homebrew/pinned").mkpath + FileUtils.ln_s HOMEBREW_CELLAR/"testball/0.1", HOMEBREW_PREFIX/"var/homebrew/pinned/testball" cask = Cask::CaskLoader.load("local-caffeine") InstallHelper.stub_cask_installation(cask) cask.pin - expect { brew "list", "--pinned", "--versions", "testball", "local-caffeine", "missing" } + expect { klass.new(["--pinned", "--versions", "testball", "local-caffeine", "missing"]).run } .to output("local-caffeine 1.2.3\ntestball 0.1\n").to_stdout - .and be_a_failure + expect(Homebrew).to have_failed cask.unpin end - it "warns for explicitly named unpinned packages", :cask, :integration_test do + it "warns for explicitly named unpinned packages", :cask do cask = Cask::CaskLoader.load("local-caffeine") InstallHelper.stub_cask_installation(cask) - expect { brew "list", "--pinned", "--cask", "local-caffeine" } + expect { klass.new(["--pinned", "--cask", "local-caffeine"]).run } .to not_to_output.to_stdout .and output(/local-caffeine not pinned/).to_stderr - .and be_a_success end - it "does not fail for unpinned Caskroom entries without named arguments", :cask, :integration_test do + it "does not fail for unpinned Caskroom entries without named arguments", :cask do (Cask::Caskroom.path/"broken").mkpath - expect { brew "list", "--pinned", "--cask" } + expect { klass.new(["--pinned", "--cask"]).run } .to not_to_output.to_stdout - .and be_a_success end end diff --git a/Library/Homebrew/test/cmd/outdated_spec.rb b/Library/Homebrew/test/cmd/outdated_spec.rb index ec3993d028ce7..1254340fbd1c8 100644 --- a/Library/Homebrew/test/cmd/outdated_spec.rb +++ b/Library/Homebrew/test/cmd/outdated_spec.rb @@ -21,6 +21,18 @@ def install_formula_version(name, version, linked: false) FileUtils.ln_s(keg_path, HOMEBREW_LINKED_KEGS/name) end + def write_formula(name, content) + Formulary.find_formula_in_tap(name, CoreTap.instance).tap do |path| + path.dirname.mkpath + path.write <<~RUBY + class #{Formulary.class_s(name)} < Formula + #{content.gsub(/^(?!$)/, " ")} + end + RUBY + CoreTap.instance.clear_cache + end + end + it "requires one named argument with --minimum-version" do expect { klass.new(["--minimum-version=1.2.3"]).run } .to raise_error(UsageError, /`--minimum-version` requires exactly one formula or cask argument/) @@ -51,9 +63,10 @@ def install_formula_version(name, version, linked: false) expect(cmd.send(:select_outdated, [cask])).to eq([cask]) end - it "outputs JSON", :integration_test do + it "outputs JSON for outdated formulae and casks", :cask, :integration_test do setup_test_formula "testball" (HOMEBREW_CELLAR/"testball/0.0.1/foo").mkpath + InstallHelper.stub_cask_installation(Cask::CaskLoader.load(cask_path("outdated/local-caffeine"))) expected_json = JSON.pretty_generate({ formulae: [{ @@ -63,7 +76,13 @@ def install_formula_version(name, version, linked: false) pinned: false, pinned_version: nil, }], - casks: [], + casks: [{ + name: "local-caffeine", + installed_versions: ["1.2.2"], + current_version: "1.2.3", + pinned: false, + pinned_version: nil, + }], }) expect { brew "outdated", "--json=v2" } @@ -71,42 +90,40 @@ def install_formula_version(name, version, linked: false) .and be_a_success end - it "reports a formula installed below the minimum version", :integration_test do - setup_test_formula "minimum-version-formula", <<~RUBY + it "reports a formula installed below the minimum version" do + write_formula "minimum-version-formula", <<~RUBY url "https://brew.sh/minimum-version-formula-1.2.3" RUBY install_formula_version "minimum-version-formula", "1.2.2" - expect { brew "outdated", "minimum-version-formula", "--min-version=1.2.3" } + expect { klass.new(["minimum-version-formula", "--min-version=1.2.3"]).run } .to output("minimum-version-formula\n").to_stdout - .and be_a_failure + expect(Homebrew).to have_failed end - it "does not report a formula installed at --minimum-version", :integration_test do - setup_test_formula "minimum-version-formula", <<~RUBY + it "does not report a formula installed at --minimum-version" do + write_formula "minimum-version-formula", <<~RUBY url "https://brew.sh/minimum-version-formula-1.2.3" RUBY install_formula_version "minimum-version-formula", "1.2.3", linked: true - expect { brew "outdated", "minimum-version-formula", "--minimum-version=1.2.3" } - .to output("").to_stdout - .and be_a_success + expect { klass.new(["minimum-version-formula", "--minimum-version=1.2.3"]).run } + .not_to output.to_stdout end - it "reports a cask installed below --minimum-version", :cask, :integration_test do + it "reports a cask installed below --minimum-version", :cask do InstallHelper.stub_cask_installation(Cask::CaskLoader.load(cask_path("outdated/local-caffeine"))) - expect { brew "outdated", "--cask", "local-caffeine", "--minimum-version=1.2.3" } + expect { klass.new(["--cask", "local-caffeine", "--minimum-version=1.2.3"]).run } .to output("local-caffeine\n").to_stdout - .and be_a_failure + expect(Homebrew).to have_failed end - it "does not report a cask installed at --minimum-version", :cask, :integration_test do + it "does not report a cask installed at --minimum-version", :cask do InstallHelper.stub_cask_installation(Cask::CaskLoader.load(cask_path("local-caffeine"))) - expect { brew "outdated", "--cask", "local-caffeine", "--minimum-version=1.2.3" } - .to output("").to_stdout - .and be_a_success + expect { klass.new(["--cask", "local-caffeine", "--minimum-version=1.2.3"]).run } + .not_to output.to_stdout end it "raises UsageError for an invalid cask --minimum-version", :cask do @@ -116,18 +133,17 @@ def install_formula_version(name, version, linked: false) .to raise_error(UsageError, %r{invalid `--minimum-version`: 1/2}) end - it "does not report an uninstalled formula with --minimum-version", :integration_test do - setup_test_formula "minimum-version-formula", <<~RUBY + it "does not report an uninstalled formula with --minimum-version" do + write_formula "minimum-version-formula", <<~RUBY url "https://brew.sh/minimum-version-formula-1.2.3" RUBY - expect { brew "outdated", "minimum-version-formula", "--minimum-version=1.2.3" } - .to output("").to_stdout - .and be_a_success + expect { klass.new(["minimum-version-formula", "--minimum-version=1.2.3"]).run } + .not_to output.to_stdout end - it "outputs JSON for a formula installed below --minimum-version", :integration_test do - setup_test_formula "minimum-version-formula", <<~RUBY + it "outputs JSON for a formula installed below --minimum-version" do + write_formula "minimum-version-formula", <<~RUBY url "https://brew.sh/minimum-version-formula-1.2.3" RUBY install_formula_version "minimum-version-formula", "1.2.2" @@ -143,12 +159,12 @@ def install_formula_version(name, version, linked: false) casks: [], }) - expect { brew "outdated", "minimum-version-formula", "--minimum-version=1.2.3", "--json=v2" } + expect { klass.new(["minimum-version-formula", "--minimum-version=1.2.3", "--json=v2"]).run } .to output("#{expected_json}\n").to_stdout - .and be_a_failure + expect(Homebrew).to have_failed end - it "outputs JSON for a cask installed below --minimum-version", :cask, :integration_test do + it "outputs JSON for a cask installed below --minimum-version", :cask do InstallHelper.stub_cask_installation(Cask::CaskLoader.load(cask_path("outdated/local-caffeine"))) expected_json = JSON.pretty_generate({ @@ -162,8 +178,8 @@ def install_formula_version(name, version, linked: false) }], }) - expect { brew "outdated", "--cask", "local-caffeine", "--minimum-version=1.2.3", "--json=v2" } + expect { klass.new(["--cask", "local-caffeine", "--minimum-version=1.2.3", "--json=v2"]).run } .to output("#{expected_json}\n").to_stdout - .and be_a_failure + expect(Homebrew).to have_failed end end diff --git a/Library/Homebrew/test/cmd/pin_spec.rb b/Library/Homebrew/test/cmd/pin_spec.rb index 56040de66f064..69aa99e72db25 100644 --- a/Library/Homebrew/test/cmd/pin_spec.rb +++ b/Library/Homebrew/test/cmd/pin_spec.rb @@ -5,6 +5,8 @@ require "cmd/shared_examples/args_parse" RSpec.describe Homebrew::Cmd::Pin do + let(:klass) { Homebrew::Cmd::Pin } + it_behaves_like "parseable arguments" it "pins a Formula's version", :integration_test do @@ -13,31 +15,36 @@ expect { brew "pin", "testball" }.to be_a_success end - it "pins a Cask's version", :cask, :integration_test do + it "pins a Cask's version", :cask do cask = Cask::CaskLoader.load("local-caffeine") InstallHelper.stub_cask_installation(cask) - expect { brew "pin", "--cask", "local-caffeine" }.to be_a_success + expect { klass.new(["--cask", "local-caffeine"]).run } + .to not_to_output.to_stderr expect(cask).to be_pinned expect(cask.pinned_version).to eq("1.2.3") cask.unpin end - it "warns when pinning a Cask with auto_updates true", :cask, :integration_test do + it "warns when pinning a Cask with auto_updates true", :cask do cask = Cask::CaskLoader.load("auto-updates") InstallHelper.stub_cask_installation(cask) expect do - expect { brew "pin", "--cask", "auto-updates" }.to be_a_success + klass.new(["--cask", "auto-updates"]).run end.to output(/auto-updates has `auto_updates true`.*outside Homebrew/).to_stderr cask.unpin end - it "fails with an uninstalled Formula", :integration_test do - setup_test_formula "testball" + it "fails with an uninstalled Formula" do + package = instance_double(Formula, pinned?: false, pinnable?: false, full_name: "testball") + cmd = klass.new(["testball"]) + allow(cmd.args.named).to receive(:to_resolved_formulae_to_casks).and_return([[package], []]) - expect { brew "pin", "testball" }.to be_a_failure + expect { cmd.run } + .to output(/testball not installed/).to_stderr + expect(Homebrew).to have_failed end end diff --git a/Library/Homebrew/test/cmd/tap-info_spec.rb b/Library/Homebrew/test/cmd/tap-info_spec.rb index 1ab3c0e23b981..895c1b2e06f06 100644 --- a/Library/Homebrew/test/cmd/tap-info_spec.rb +++ b/Library/Homebrew/test/cmd/tap-info_spec.rb @@ -18,11 +18,13 @@ .and be_a_success end - it "display brief statistics for all installed taps", :integration_test, :needs_network do - expect { brew "tap-info" } + it "display brief statistics for all installed taps" do + tap = instance_double(Tap, formula_files: [], command_files: [], private?: false) + allow(Tap).to receive(:installed).and_return([tap]) + + expect { klass.new([]).run } .to output(/\d+ taps?, \d+ private/).to_stdout .and not_to_output.to_stderr - .and be_a_success end describe "#decorate_formula" do diff --git a/Library/Homebrew/test/cmd/unalias_spec.rb b/Library/Homebrew/test/cmd/unalias_spec.rb index 46f4b7d6d7fe4..9751a534c94c7 100644 --- a/Library/Homebrew/test/cmd/unalias_spec.rb +++ b/Library/Homebrew/test/cmd/unalias_spec.rb @@ -8,21 +8,22 @@ it_behaves_like "parseable arguments" it "unsets an alias", :integration_test do - expect { brew "alias", "foo=bar" } + (HOMEBREW_PREFIX/"bin").mkpath + Homebrew::Aliases.init + + expect { Homebrew::Aliases.add("foo", "bar") } .to not_to_output.to_stdout .and not_to_output.to_stderr - .and be_a_success - expect { brew "alias" } + expect { Homebrew::Aliases.show } .to output(/brew alias foo='bar'/).to_stdout .and not_to_output.to_stderr - .and be_a_success + expect { brew "unalias", "foo" } .to not_to_output.to_stdout .and not_to_output.to_stderr .and be_a_success - expect { brew "alias" } + expect { Homebrew::Aliases.show } .to not_to_output.to_stdout .and not_to_output.to_stderr - .and be_a_success end end diff --git a/Library/Homebrew/test/cmd/unpin_spec.rb b/Library/Homebrew/test/cmd/unpin_spec.rb index f8f79ce3cf201..ead4e35e6a3c9 100644 --- a/Library/Homebrew/test/cmd/unpin_spec.rb +++ b/Library/Homebrew/test/cmd/unpin_spec.rb @@ -5,6 +5,8 @@ require "cmd/unpin" RSpec.describe Homebrew::Cmd::Unpin do + let(:klass) { Homebrew::Cmd::Unpin } + it_behaves_like "parseable arguments" it "unpins a Formula's version", :integration_test do @@ -14,17 +16,18 @@ expect { brew "unpin", "testball" }.to be_a_success end - it "unpins a Cask's version", :cask, :integration_test do + it "unpins a Cask's version", :cask do cask = Cask::CaskLoader.load("local-caffeine") InstallHelper.stub_cask_installation(cask) cask.pin - expect { brew "unpin", "--cask", "local-caffeine" }.to be_a_success + expect { klass.new(["--cask", "local-caffeine"]).run } + .to not_to_output.to_stderr expect(cask).not_to be_pinned end - it "removes a dangling Cask pin", :cask, :integration_test do + it "removes a dangling Cask pin", :cask do cask = Cask::CaskLoader.load("local-caffeine") InstallHelper.stub_cask_installation(cask) cask.pin @@ -33,7 +36,8 @@ expect(cask).not_to be_pinned expect(cask.pin_path).to be_a_symlink - expect { brew "unpin", "--cask", "local-caffeine" }.to be_a_success + expect { klass.new(["--cask", "local-caffeine"]).run } + .to not_to_output.to_stderr expect(cask.pin_path).not_to be_a_symlink end diff --git a/Library/Homebrew/test/cmd/untap_spec.rb b/Library/Homebrew/test/cmd/untap_spec.rb index 9331b9b3a9ef8..7844579f348b4 100644 --- a/Library/Homebrew/test/cmd/untap_spec.rb +++ b/Library/Homebrew/test/cmd/untap_spec.rb @@ -20,11 +20,18 @@ .and be_a_success end - describe "#installed_formulae_for", :integration_test do + describe "#installed_formulae_for" do shared_examples "finds installed formulae in tap", :no_api do def load_formula(name:, with_formula_file: false, mock_install: false) formula = if with_formula_file - path = setup_test_formula(name, tap:) + path = Formulary.find_formula_in_tap(name, tap) + path.dirname.mkpath + path.write <<~RUBY + class #{Formulary.class_s(name)} < Formula + url "https://brew.sh/#{name}-1.0.tgz" + end + RUBY + tap.clear_cache Formulary.factory(path) else formula(name, tap:) do diff --git a/Library/Homebrew/test/cmd/upgrade_spec.rb b/Library/Homebrew/test/cmd/upgrade_spec.rb index de209df4bd00e..010597bd10ea5 100644 --- a/Library/Homebrew/test/cmd/upgrade_spec.rb +++ b/Library/Homebrew/test/cmd/upgrade_spec.rb @@ -24,77 +24,116 @@ def install_formula_version(name, version, optlinked: false) FileUtils.ln_s(keg_path, HOMEBREW_PREFIX/"opt/#{name}") end - it "upgrades a Formula", :integration_test do + def write_formula(name, content) + Formulary.find_formula_in_tap(name, CoreTap.instance).tap do |path| + path.dirname.mkpath + path.write <<~RUBY + class #{Formulary.class_s(name)} < Formula + #{content.gsub(/^(?!$)/, " ")} + end + RUBY + CoreTap.instance.clear_cache + end + end + + it "upgrades a Formula and Cask", :cask, :integration_test do formula_name = "testball_bottle" formula_rack = HOMEBREW_CELLAR/formula_name setup_test_formula formula_name + mktmpdir do |dir| + (dir/"local-upgrade-test.rb").write <<~RUBY + cask "local-upgrade-test" do + version "1.0" + sha256 :no_check + url "file://#{TEST_FIXTURE_DIR}/cask/caffeine.zip" + stage_only true + end + RUBY + (CoreCaskTap.instance.cask_dir/"local-upgrade-test.rb").write <<~RUBY + cask "local-upgrade-test" do + version "2.0" + sha256 :no_check + url "file://#{TEST_FIXTURE_DIR}/cask/caffeine.zip" + stage_only true + end + RUBY + CoreCaskTap.instance.clear_cache + InstallHelper.stub_cask_installation(Cask::CaskLoader.load(dir/"local-upgrade-test.rb")) + + (formula_rack/"0.0.1/foo").mkpath + + expect do + brew "upgrade", formula_name, "local-upgrade-test" + end.to be_a_success + + expect(formula_rack/"0.1").to be_a_directory + expect(formula_rack/"0.0.1").not_to exist + expect(Cask::CaskLoader.load("local-upgrade-test").installed_version).to eq("2.0") + end + end - (formula_rack/"0.0.1/foo").mkpath - - expect { brew "upgrade" }.to be_a_success - - expect(formula_rack/"0.1").to be_a_directory - expect(formula_rack/"0.0.1").not_to exist - - uninstall_test_formula formula_name + # links newer version when upgrade was interrupted + it "links a newer Formula version when upgrade was interrupted" do + formula_name = "testball_bottle" + formula_rack = HOMEBREW_CELLAR/formula_name + write_formula formula_name, <<~RUBY + url "file://#{TEST_FIXTURE_DIR}/tarballs/testball-0.1.tbz" + sha256 TESTBALL_SHA256 - # links newer version when upgrade was interrupted - (formula_rack/"0.1/foo").mkpath + bottle do + root_url "file://#{TEST_FIXTURE_DIR}/bottles" + sha256 cellar: :any_skip_relocation, all: "d7b9f4e8bf83608b71fe958a99f19f2e5e68bb2582965d32e41759c24f1aef97" + end + RUBY + install_formula_version formula_name, "0.1" - expect { brew "upgrade" }.to be_a_success + expect { klass.new([]).run }.not_to raise_error expect(formula_rack/"0.1").to be_a_directory expect(HOMEBREW_PREFIX/"opt/#{formula_name}").to be_a_symlink expect(HOMEBREW_PREFIX/"var/homebrew/linked/#{formula_name}").to be_a_symlink + end - uninstall_test_formula formula_name - - # upgrades with asking for user prompts - (formula_rack/"0.0.1/foo").mkpath - - expect { brew "upgrade", "--ask" } - .to output(/==> Would upgrade 1 outdated package\n#{formula_name} 0\.1/).to_stdout - .and output(/✔︎.*/m).to_stderr - - expect(formula_rack/"0.1").to be_a_directory - expect(formula_rack/"0.0.1").not_to exist - - uninstall_test_formula formula_name - - # refuses to upgrade a forbidden formula + # refuses to upgrade a forbidden formula + it "refuses to upgrade a forbidden Formula" do + formula_name = "testball_bottle" + formula_rack = HOMEBREW_CELLAR/formula_name + write_formula formula_name, <<~RUBY + url "https://brew.sh/#{formula_name}-0.1" + RUBY (formula_rack/"0.0.1/foo").mkpath - expect { brew "upgrade", formula_name, { "HOMEBREW_FORBIDDEN_FORMULAE" => formula_name } } - .to not_to_output(%r{#{formula_rack}/0\.1}o).to_stdout - .and output(/#{formula_name} was forbidden/).to_stderr - .and be_a_failure + with_env("HOMEBREW_FORBIDDEN_FORMULAE" => formula_name) do + expect { klass.new([formula_name]).run } + .to not_to_output(%r{#{formula_rack}/0\.1}o).to_stdout + .and output(/#{formula_name} was forbidden/).to_stderr + end + expect(Homebrew).to have_failed expect(formula_rack/"0.1").not_to exist end - it "upgrades a named formula installed below the minimum version", :integration_test do - setup_test_formula "minimum-version-formula", <<~RUBY + it "upgrades a named formula installed below the minimum version" do + write_formula "minimum-version-formula", <<~RUBY url "https://brew.sh/minimum-version-formula-1.2.3" RUBY install_formula_version "minimum-version-formula", "1.2.2", optlinked: true - expect { brew "upgrade", "minimum-version-formula", "--min-version=1.2.3", "--dry-run" } + expect { klass.new(["minimum-version-formula", "--min-version=1.2.3", "--dry-run"]).run } .to output(/minimum-version-formula 1\.2\.2 -> 1\.2\.3/).to_stdout - .and be_a_success end - it "does not upgrade a named formula installed at --minimum-version", :integration_test do - setup_test_formula "minimum-version-formula", <<~RUBY + it "does not upgrade a named formula installed at --minimum-version" do + write_formula "minimum-version-formula", <<~RUBY url "https://brew.sh/minimum-version-formula-1.2.4" RUBY install_formula_version "minimum-version-formula", "1.2.3", optlinked: true - expect { brew "upgrade", "minimum-version-formula", "--minimum-version=1.2.3", "--dry-run" } + expect { klass.new(["minimum-version-formula", "--minimum-version=1.2.3", "--dry-run"]).run } .to not_to_output(/Would upgrade/).to_stdout .and output( /Not upgrading minimum-version-formula, the installed version is not below the minimum version 1\.2\.3/, ).to_stderr - .and be_a_success end it "requires one named argument with --minimum-version" do @@ -107,22 +146,20 @@ def install_formula_version(name, version, optlinked: false) .to raise_error(UsageError, /`--minimum-version` requires exactly one formula or cask argument/) end - it "upgrades a named cask installed below --minimum-version", :cask, :integration_test do + it "upgrades a named cask installed below --minimum-version", :cask do InstallHelper.stub_cask_installation(Cask::CaskLoader.load(cask_path("outdated/local-caffeine"))) - expect { brew "upgrade", "--cask", "local-caffeine", "--minimum-version=1.2.3", "--dry-run" } + expect { klass.new(["--cask", "local-caffeine", "--minimum-version=1.2.3", "--dry-run"]).run } .to output(/local-caffeine 1\.2\.2 -> 1\.2\.3/).to_stdout - .and be_a_success end - it "does not upgrade a named cask installed at --minimum-version", :cask, :integration_test do + it "does not upgrade a named cask installed at --minimum-version", :cask do InstallHelper.stub_cask_installation(Cask::CaskLoader.load(cask_path("local-caffeine"))) - expect { brew "upgrade", "--cask", "local-caffeine", "--minimum-version=1.2.3", "--dry-run" } + expect { klass.new(["--cask", "local-caffeine", "--minimum-version=1.2.3", "--dry-run"]).run } .to not_to_output(/Would upgrade/).to_stdout .and output(/Not upgrading local-caffeine, the installed version is not below the minimum version 1\.2\.3/) .to_stderr - .and be_a_success end it "reports unavailable names via ofail and continues upgrading" do @@ -186,6 +223,7 @@ def install_formula_version(name, version, optlinked: false) end end + # upgrades with asking for user prompts it "prints formula and cask ask plans before upgrading" do cmd = klass.new(["--ask"]) diff --git a/Library/Homebrew/test/cmd/uses_spec.rb b/Library/Homebrew/test/cmd/uses_spec.rb index 63a34ae5711b9..1683cefc79642 100644 --- a/Library/Homebrew/test/cmd/uses_spec.rb +++ b/Library/Homebrew/test/cmd/uses_spec.rb @@ -49,18 +49,17 @@ .and be_a_success end - it "handles unavailable formula", :integration_test, :no_api do - setup_test_formula "foo" - setup_test_formula "bar" - setup_test_formula "optional", <<~RUBY - url "https://brew.sh/optional-1.0" - depends_on "bar" => :optional - RUBY - + it "handles unavailable formula" do expect_any_instance_of(Homebrew::CLI::NamedArgs) .to receive(:to_formulae) .and_raise(FormulaUnavailableError, "foo") cmd = klass.new(%w[foo --eval-all --include-optional --recursive]) + allow(cmd).to receive(:intersection_of_dependents) + .and_return([ + instance_double(Formula, full_name: "bar"), + instance_double(Formula, full_name: "optional"), + ]) + expect { cmd.run } .to output(/^(bar\noptional|optional\nbar)$/).to_stdout .and output(/Error: Missing formulae should not have dependents!\n/).to_stderr diff --git a/Library/Homebrew/test/cmd/which-formula_spec.rb b/Library/Homebrew/test/cmd/which-formula_spec.rb index 230d3c98a104d..8b7fe1fa1d240 100644 --- a/Library/Homebrew/test/cmd/which-formula_spec.rb +++ b/Library/Homebrew/test/cmd/which-formula_spec.rb @@ -1,6 +1,8 @@ # typed: false # frozen_string_literal: true +require "open3" + require "cmd/shared_examples/args_parse" require "cmd/which-formula" @@ -10,7 +12,6 @@ it_behaves_like "parseable arguments" describe "which_formula" do - let(:brew_sh_env) { { "HOMEBREW_COLOR" => nil, "HOMEBREW_NO_EMOJI" => nil } } let(:shell_cellar) do if (HOMEBREW_LIBRARY_PATH.parent.parent/"Cellar").directory? HOMEBREW_LIBRARY_PATH.parent.parent/"Cellar" @@ -41,23 +42,65 @@ FileUtils.rm_rf shell_cellar/"foo" end - it "prints plain formula names when outputting to a non-TTY", :integration_test do - expect { brew_sh "which-formula", "foo2", brew_sh_env }.to output("foo\n").to_stdout - expect do - brew_sh "which-formula", "foo2", brew_sh_env.merge("HOMEBREW_NO_EMOJI" => "1") - end.to output("foo\n").to_stdout - expect { brew_sh "which-formula", "baz", brew_sh_env }.to output("baz\n").to_stdout - expect { brew_sh "which-formula", "bar" }.not_to output.to_stdout - expect { brew_sh "which-formula", "QUX", brew_sh_env }.to output("qux\n").to_stdout - expect { brew_sh "which-formula", "quux", brew_sh_env }.to output("quux\n").to_stdout - end + it "finds formulae using the Bash command path" do + env = { + "HOMEBREW_BREW_FILE" => HOMEBREW_BREW_FILE.to_s, + "HOMEBREW_CACHE" => HOMEBREW_CACHE.to_s, + "HOMEBREW_CELLAR" => shell_cellar.to_s, + "HOMEBREW_LIBRARY" => HOMEBREW_LIBRARY_PATH.parent.to_s, + } + env["HOMEBREW_MACOS"] = "1" if OS.mac? + stdout, stderr, status = Open3.capture3( + env, + "/bin/bash", "-c", <<~SH, + source "$1" + + stdout_file="$(mktemp)" + stderr_file="$(mktemp)" + trap 'rm -f "${stdout_file}" "${stderr_file}"' EXIT + + check() { + local label="$1" + local expected_status="$2" + local expected_stdout="$3" + local expected_stderr="$4" + shift 4 + + ( "$@" ) >"${stdout_file}" 2>"${stderr_file}" + status="$?" + if [[ "${status}" -ne "${expected_status}" ]] + then + echo "${label}: expected status ${expected_status}, got ${status}" >&2 + return 1 + fi + if ! diff -u <(printf '%s' "${expected_stdout}") "${stdout_file}" >&2 + then + echo "${label}: stdout mismatch" >&2 + return 1 + fi + if ! diff -u <(printf '%s' "${expected_stderr}") "${stderr_file}" >&2 + then + echo "${label}: stderr mismatch" >&2 + return 1 + fi + } + + check "installed and uninstalled executables" 0 $'foo\\nbaz\\nqux\\nquux\\n' "" \\ + homebrew-which-formula foo2 baz QUX quux + HOMEBREW_NO_EMOJI=1 check "non-emoji output" 0 $'foo\\n' "" homebrew-which-formula foo2 + check "missing executable" 1 "" "" homebrew-which-formula bar - it "errors if the API is disabled and the executable database is missing", :integration_test do - klass::DATABASE_FILE.unlink + rm -f "$(executables_txt_cache_file)" + HOMEBREW_NO_INSTALL_FROM_API=1 check "disabled API without database" 1 "" \\ + $'Error: HOMEBREW_NO_INSTALL_FROM_API must be unset to use `brew which-formula` or `brew exec`.\\n' \\ + homebrew-which-formula foo2 + SH + "bash", (HOMEBREW_LIBRARY_PATH/"cmd/which-formula.sh").to_s + ) - expect do - expect(brew_sh("which-formula", "foo2", "HOMEBREW_NO_INSTALL_FROM_API" => "1")).to be_a_failure - end.to output(/HOMEBREW_NO_INSTALL_FROM_API must be unset/).to_stderr + expect(status).to be_success + expect(stdout).to be_empty + expect(stderr).to be_empty end end end diff --git a/Library/Homebrew/test/dev-cmd/bump_spec.rb b/Library/Homebrew/test/dev-cmd/bump_spec.rb index 71369e9f4d5e0..11295444af463 100644 --- a/Library/Homebrew/test/dev-cmd/bump_spec.rb +++ b/Library/Homebrew/test/dev-cmd/bump_spec.rb @@ -57,11 +57,11 @@ end end - it "gives an error for `--tap` with official taps", :integration_test do - expect { brew "bump", "--tap", "Homebrew/core" } - .to output(/Invalid usage/).to_stderr - .and not_to_output.to_stdout - .and be_a_failure + it "gives an error for `--tap` with official taps" do + allow(Homebrew).to receive(:install_bundler_gems!) + + expect { klass.new(["--tap", "Homebrew/core"]).run } + .to raise_error(UsageError, /`--tap` requires `--auto` for official taps/) end describe "::skip_ineligible_formulae!" do diff --git a/Library/Homebrew/test/dev-cmd/extract_spec.rb b/Library/Homebrew/test/dev-cmd/extract_spec.rb index 8ad02c2e76a35..4d32b2ed24929 100644 --- a/Library/Homebrew/test/dev-cmd/extract_spec.rb +++ b/Library/Homebrew/test/dev-cmd/extract_spec.rb @@ -16,19 +16,26 @@ core_tap.path.cd do system "git", "init" # Start with deprecated bottle syntax - setup_test_formula "testball", bottle_block: <<~EOS + formula_file = Formulary.find_formula_in_tap("testball", core_tap) + formula_file.dirname.mkpath + formula_file.write <<~RUBY + class Testball < Formula + url "https://brew.sh/testball-0.1.tar.gz" + + bottle do + cellar :any + end - bottle do - cellar :any end - EOS + RUBY system "git", "add", "--all" system "git", "commit", "-m", "testball 0.1" # Replace with a valid formula for the next version - formula_file = setup_test_formula "testball" - contents = File.read(formula_file) - contents.gsub!("testball-0.1", "testball-0.2") - File.write(formula_file, contents) + formula_file.write <<~RUBY + class Testball < Formula + url "https://brew.sh/testball-0.2.tar.gz" + end + RUBY system "git", "add", "--all" system "git", "commit", "-m", "testball 0.2" end @@ -45,22 +52,18 @@ expect(Formulary.factory(path).version).to eq "0.2" end - it "retrieves the specified version of formula", :integration_test do + it "retrieves the specified version of formula" do path = target[:path]/"Formula/testball@0.1.rb" - expect { brew "extract", "testball", target[:name], "--version=0.1" } + expect { Homebrew::DevCmd::Extract.new(["testball", target[:name], "--version=0.1"]).run } .to output(/^#{path}$/).to_stdout - .and not_to_output.to_stderr - .and be_a_success expect(path).to exist expect(Formulary.factory(path).version).to eq "0.1" end - it "retrieves the compatible version of formula", :integration_test do + it "retrieves the compatible version of formula" do path = target[:path]/"Formula/testball@0.rb" - expect { brew "extract", "testball", target[:name], "--version=0" } + expect { Homebrew::DevCmd::Extract.new(["testball", target[:name], "--version=0"]).run } .to output(/^#{path}$/).to_stdout - .and not_to_output.to_stderr - .and be_a_success expect(path).to exist expect(Formulary.factory(path).version).to eq "0.2" end diff --git a/Library/Homebrew/test/dev-cmd/formula_spec.rb b/Library/Homebrew/test/dev-cmd/formula_spec.rb index 095053377a51f..acd3867480e6d 100644 --- a/Library/Homebrew/test/dev-cmd/formula_spec.rb +++ b/Library/Homebrew/test/dev-cmd/formula_spec.rb @@ -8,7 +8,9 @@ it_behaves_like "parseable arguments" it "prints a given Formula's path", :integration_test do - formula_file = setup_test_formula "testball" + formula_file = Formulary.find_formula_in_tap("testball", CoreTap.instance) + formula_file.dirname.mkpath + formula_file.write "" expect { brew "formula", "testball" } .to output("#{formula_file}\n").to_stdout diff --git a/Library/Homebrew/test/dev-cmd/linkage_spec.rb b/Library/Homebrew/test/dev-cmd/linkage_spec.rb index c1a2c6ed277f1..9c461902eddd4 100644 --- a/Library/Homebrew/test/dev-cmd/linkage_spec.rb +++ b/Library/Homebrew/test/dev-cmd/linkage_spec.rb @@ -8,24 +8,18 @@ it_behaves_like "parseable arguments" it "works when no arguments are provided", :integration_test do - setup_test_formula "testball" - (HOMEBREW_CELLAR/"testball/0.0.1/foo").mkpath - expect { brew "linkage" } .to be_a_success .and not_to_output.to_stdout .and not_to_output.to_stderr end - it "accepts no_linkage dependency tag", :integration_test do - setup_test_formula "testball" do + it "accepts no_linkage dependency tag" do + expect(formula("testball") do url "file://#{TEST_FIXTURE_DIR}/tarballs/testball-0.1.tbz" sha256 TESTBALL_SHA256 depends_on "foo" => :no_linkage - end - - expect { brew "info", "testball" } - .to be_a_success + end.deps.first).to be_no_linkage end end diff --git a/Library/Homebrew/test/dev-cmd/livecheck_spec.rb b/Library/Homebrew/test/dev-cmd/livecheck_spec.rb index 8bee29fb16065..8154c19abd5be 100644 --- a/Library/Homebrew/test/dev-cmd/livecheck_spec.rb +++ b/Library/Homebrew/test/dev-cmd/livecheck_spec.rb @@ -5,6 +5,8 @@ require "dev-cmd/livecheck" RSpec.describe Homebrew::DevCmd::LivecheckCmd do + let(:klass) { Homebrew::DevCmd::LivecheckCmd } + it_behaves_like "parseable arguments" it "reports the latest version of a Formula", :integration_test, :needs_network do @@ -21,10 +23,12 @@ .and be_a_success end - it "gives an error when no arguments are given and there's no watchlist", :integration_test do - expect { brew "livecheck", "HOMEBREW_LIVECHECK_WATCHLIST" => ".this_should_not_exist" } - .to output(/Invalid usage: `brew livecheck` with no arguments needs a watchlist file to be present/).to_stderr - .and not_to_output.to_stdout - .and be_a_failure + it "gives an error when no arguments are given and there's no watchlist" do + allow(Homebrew).to receive(:install_bundler_gems!) + + with_env("HOMEBREW_LIVECHECK_WATCHLIST" => ".this_should_not_exist") do + expect { klass.new([]).run } + .to raise_error(UsageError, /`brew livecheck` with no arguments needs a watchlist file to be present/) + end end end diff --git a/Library/Homebrew/test/dev-cmd/ruby_spec.rb b/Library/Homebrew/test/dev-cmd/ruby_spec.rb index 46133e825ea58..5bb87eed18270 100644 --- a/Library/Homebrew/test/dev-cmd/ruby_spec.rb +++ b/Library/Homebrew/test/dev-cmd/ruby_spec.rb @@ -5,6 +5,8 @@ require "dev-cmd/ruby" RSpec.describe Homebrew::DevCmd::Ruby do + let(:klass) { Homebrew::DevCmd::Ruby } + it_behaves_like "parseable arguments" it "executes ruby code with Homebrew's libraries loaded", :integration_test do @@ -14,18 +16,17 @@ .and not_to_output.to_stderr end - # Doesn't actually need Linux but only running there as integration tests are slow. - describe "-e 'puts \"testball\".f.path'", :integration_test, :needs_linux do - let!(:target) do - target_path = setup_test_formula "testball" - { path: target_path } - end + # Keep the richer expression path in-process as `brew ruby` subprocesses are slow. + it "passes Homebrew libraries and code to Ruby" do + cmd = klass.new(["-e", "puts 'testball'.f.path"]) + + expect(cmd).to receive(:exec).with( + *HOMEBREW_RUBY_EXEC_ARGS, + "-I", $LOAD_PATH.join(File::PATH_SEPARATOR), + "-rglobal", "-rbrew_irb_helpers", + "-e puts 'testball'.f.path" + ) - it "prints the path of a test formula" do - expect { brew "ruby", "-e", "puts 'testball'.f.path" } - .to be_a_success - .and output(/^#{target[:path]}$/).to_stdout - .and not_to_output.to_stderr - end + cmd.run end end diff --git a/Library/Homebrew/test/dev-cmd/unpack_spec.rb b/Library/Homebrew/test/dev-cmd/unpack_spec.rb index 192b9c45bb719..f3042cf20df47 100644 --- a/Library/Homebrew/test/dev-cmd/unpack_spec.rb +++ b/Library/Homebrew/test/dev-cmd/unpack_spec.rb @@ -18,12 +18,12 @@ end end - it "unpacks a given Cask's archive", :integration_test do + it "unpacks a given Cask's archive" do caffeine_cask = Cask::CaskLoader.load(cask_path("local-caffeine")) mktmpdir do |path| - expect { brew "unpack", cask_path("local-caffeine"), "--destdir=#{path}" } - .to be_a_success + expect { Homebrew::DevCmd::Unpack.new([cask_path("local-caffeine").to_s, "--destdir=#{path}"]).run } + .not_to raise_error expect(path/"local-caffeine-#{caffeine_cask.version}").to be_a_directory end diff --git a/Library/Homebrew/test/download_strategies/subversion_spec.rb b/Library/Homebrew/test/download_strategies/subversion_spec.rb index 9427229414848..70eac580f3490 100644 --- a/Library/Homebrew/test/download_strategies/subversion_spec.rb +++ b/Library/Homebrew/test/download_strategies/subversion_spec.rb @@ -2,6 +2,7 @@ # frozen_string_literal: true require "download_strategy" +require "utils/svn" RSpec.describe SubversionDownloadStrategy do subject(:strategy) { klass.new(url, name, version, **specs) } @@ -13,9 +14,17 @@ let(:specs) { {} } describe "#fetch" do + before do + allow(strategy).to receive(:repo_url).and_return("#{url}/old") + end + context "with :trust_cert set" do let(:specs) { { trust_cert: true } } + before do + allow(Utils::Svn).to receive(:version).and_return("1.14.5") + end + it "adds the appropriate svn args" do expect(strategy).to receive(:system_command!) .with("svn", hash_including(args: array_including("--trust-server-cert", "--non-interactive"))) diff --git a/Library/Homebrew/test/installed_dependents_spec.rb b/Library/Homebrew/test/installed_dependents_spec.rb index d8d756c2fe851..39fe2617c92f2 100644 --- a/Library/Homebrew/test/installed_dependents_spec.rb +++ b/Library/Homebrew/test/installed_dependents_spec.rb @@ -43,7 +43,31 @@ def setup_test_keg(name, version, &block) describe "::find_some_installed_dependents" do def setup_test_keg(name, version, &block) keg = super - Tab.create(keg.to_formula, DevelopmentTools.default_compiler, :libcxx).write + tab = Tab.new( + "homebrew_version" => HOMEBREW_VERSION, + "installed_on_request" => false, + "loaded_from_api" => false, + "loaded_from_internal_api" => false, + "source" => { + "path" => nil, + "tap" => "homebrew/core", + "tap_git_head" => nil, + "spec" => "stable", + "versions" => { + "stable" => version, + "head" => nil, + "version_scheme" => 0, + "compatibility_version" => nil, + }, + }, + "built_on" => {}, + ) + tab.tabfile = keg/AbstractTab::FILENAME + tab.stdlib = :libcxx + tab.compiler = DevelopmentTools.default_compiler + tab.aliases = [] + tab.runtime_dependencies = [] + tab.write keg end diff --git a/Library/Homebrew/test/spec_helper.rb b/Library/Homebrew/test/spec_helper.rb index c93b785955d10..2534028f6ace0 100644 --- a/Library/Homebrew/test/spec_helper.rb +++ b/Library/Homebrew/test/spec_helper.rb @@ -205,28 +205,46 @@ ENV["HOMEBREW_NO_INSTALL_FROM_API"] = "1" end + svn_path_dirs = nil + svn_skip_reason = nil + config.before(:each, :needs_svn) do - svn_shim = HOMEBREW_SHIMS_PATH/"shared/svn" - skip "Subversion is not installed." unless quiet_system svn_shim, "--version" + skip svn_skip_reason if svn_skip_reason + if svn_path_dirs + ENV["PATH"] = PATH.new(ENV.fetch("PATH")).append(svn_path_dirs) + next + end - svn_shim_path = Pathname(Utils.popen_read(svn_shim, "--homebrew=print-path").chomp.presence) svn_paths = PATH.new(ENV.fetch("PATH")) - svn_paths.prepend(svn_shim_path.dirname) if OS.mac? xcrun_svn = Utils.popen_read("xcrun", "-f", "svn") svn_paths.append(File.dirname(xcrun_svn)) if $CHILD_STATUS.success? && xcrun_svn.present? end - svn = which("svn", svn_paths) - skip "svn is not installed." unless svn - svnadmin = which("svnadmin", svn_paths) - skip "svnadmin is not installed." unless svnadmin + unless svnadmin + svn_skip_reason = "svnadmin is not installed." + skip svn_skip_reason + end + + svn_shim = HOMEBREW_SHIMS_PATH/"shared/svn" + unless quiet_system svn_shim, "--version" + svn_skip_reason = "Subversion is not installed." + skip svn_skip_reason + end + + svn_shim_path = Pathname(Utils.popen_read(svn_shim, "--homebrew=print-path").chomp.presence) + svn_paths.prepend(svn_shim_path.dirname) + + svn = which("svn", svn_paths) + unless svn + svn_skip_reason = "svn is not installed." + skip svn_skip_reason + end - ENV["PATH"] = PATH.new(ENV.fetch("PATH")) - .append(svn.dirname) - .append(svnadmin.dirname) + svn_path_dirs = [svn.dirname, svnadmin.dirname] + ENV["PATH"] = PATH.new(ENV.fetch("PATH")).append(svn_path_dirs) end config.before(:each, :needs_homebrew_curl) do diff --git a/Library/Homebrew/test/unpack_strategy/dmg_spec.rb b/Library/Homebrew/test/unpack_strategy/dmg_spec.rb index 0ad05af11c556..9b854c0dd155a 100644 --- a/Library/Homebrew/test/unpack_strategy/dmg_spec.rb +++ b/Library/Homebrew/test/unpack_strategy/dmg_spec.rb @@ -8,6 +8,21 @@ let(:path) { TEST_FIXTURE_DIR/"cask/container.dmg" } include_examples "UnpackStrategy::detect" - include_examples "#extract", children: ["container"] + + specify "#extract" do + Dir.mktmpdir do |dir| + unpack_dir = Pathname(dir) + mount = instance_double(UnpackStrategy::Dmg.const_get(:Mount, false)) + unpack_strategy = UnpackStrategy::Dmg.new(path) + + allow(unpack_strategy).to receive(:mount).with(verbose: false).and_yield([mount]) + allow(mount).to receive(:extract).with(to: unpack_dir, verbose: false) do + (unpack_dir/"container").mkpath + end + + unpack_strategy.extract(to: unpack_dir) + expect(unpack_dir.children(false).map(&:to_s)).to contain_exactly("container") + end + end end end diff --git a/Library/Homebrew/test/utils/svn_spec.rb b/Library/Homebrew/test/utils/svn_spec.rb index 2e6b8505447a6..02d29605e0bb0 100644 --- a/Library/Homebrew/test/utils/svn_spec.rb +++ b/Library/Homebrew/test/utils/svn_spec.rb @@ -6,31 +6,41 @@ RSpec.describe Utils::Svn do let(:klass) { Utils::Svn } + def svn_result(stdout = "", success:, stderr: "") + status = instance_double(Process::Status, success?: success) + instance_double(SystemCommand::Result, to_a: [stdout, stderr, status]) + end + before do klass.clear_version_cache end describe "::available?" do - it "returns svn version if svn available" do - if quiet_system "#{HOMEBREW_SHIMS_PATH}/shared/svn", "--version" - expect(klass).to be_available - else - expect(klass).not_to be_available - end + it "returns true when svn version is present" do + allow(klass).to receive(:version).and_return("1.14.5") + expect(klass).to be_available + end + + it "returns false when svn version is missing" do + allow(klass).to receive(:version).and_return(nil) + expect(klass).not_to be_available end end describe "::version" do - it "returns svn version if svn available" do - if quiet_system "#{HOMEBREW_SHIMS_PATH}/shared/svn", "--version" - expect(klass.version).to match(/^\d+\.\d+\.\d+$/) - else - expect(klass.version).to be_nil - end - end + it "returns svn version or nil" do + expect(klass).to receive(:system_command) + .with(HOMEBREW_SHIMS_PATH/"shared/svn", args: ["--version"], print_stderr: false) + .and_return(svn_result("svn, version 1.14.5\n", success: true)) + + expect(klass.version).to eq("1.14.5") - it "returns version of svn when svn is available", :needs_svn do - expect(klass.version).not_to be_nil + klass.clear_version_cache + expect(klass).to receive(:system_command) + .with(HOMEBREW_SHIMS_PATH/"shared/svn", args: ["--version"], print_stderr: false) + .and_return(svn_result("", success: false)) + + expect(klass.version).to be_nil end end @@ -46,6 +56,10 @@ end it "returns false when remote does not exist" do + expect(klass).to receive(:system_command) + .with("svn", args: ["ls", "blah", "--depth", "empty"], print_stderr: false) + .and_return(svn_result(success: false)) + expect(klass).not_to be_remote_exists("blah") end