 990c1efc16
			
		
	
	
		990c1efc16
		
			
		
	
	
	
	
		
			
			- Homebrew Bundle referred to formulae as "brews". But it referred to casks as "casks" and taps as "taps". - Let's use the same terminology everywhere. - (I know that `brew "hello"` is the formula syntax in the Brewfile, so I'm not changing that (though would be up for it, in a backwards compatible manner), just making the code more consistent.)
		
			
				
	
	
		
			309 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Ruby
		
	
	
	
	
	
			
		
		
	
	
			309 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Ruby
		
	
	
	
	
	
| # frozen_string_literal: true
 | |
| 
 | |
| require "bundle"
 | |
| require "bundle/commands/check"
 | |
| require "bundle/brew_checker"
 | |
| require "bundle/cask_checker"
 | |
| require "bundle/mac_app_store_checker"
 | |
| require "bundle/vscode_extension_checker"
 | |
| require "bundle/formula_installer"
 | |
| require "bundle/cask_installer"
 | |
| require "bundle/mac_app_store_installer"
 | |
| require "bundle/dsl"
 | |
| require "bundle/skipper"
 | |
| 
 | |
| RSpec.describe Homebrew::Bundle::Commands::Check, :no_api do
 | |
|   let(:do_check) do
 | |
|     described_class.run(no_upgrade:, verbose:)
 | |
|   end
 | |
|   let(:no_upgrade) { false }
 | |
|   let(:verbose) { false }
 | |
| 
 | |
|   before do
 | |
|     Homebrew::Bundle::Checker.reset!
 | |
|     allow_any_instance_of(IO).to receive(:puts)
 | |
|     stub_formula_loader formula("mas") { url "mas-1.0" }
 | |
|   end
 | |
| 
 | |
|   context "when dependencies are satisfied" do
 | |
|     it "does not raise an error" do
 | |
|       allow_any_instance_of(Pathname).to receive(:read).and_return("")
 | |
|       nothing = []
 | |
|       allow(Homebrew::Bundle::Checker).to receive_messages(casks_to_install:      nothing,
 | |
|                                                            formulae_to_install:   nothing,
 | |
|                                                            apps_to_install:       nothing,
 | |
|                                                            taps_to_tap:           nothing,
 | |
|                                                            extensions_to_install: nothing)
 | |
|       expect { do_check }.not_to raise_error
 | |
|     end
 | |
|   end
 | |
| 
 | |
|   context "when no dependencies are specified" do
 | |
|     it "does not raise an error" do
 | |
|       allow_any_instance_of(Pathname).to receive(:read).and_return("")
 | |
|       allow_any_instance_of(Homebrew::Bundle::Dsl).to receive(:entries).and_return([])
 | |
|       expect { do_check }.not_to raise_error
 | |
|     end
 | |
|   end
 | |
| 
 | |
|   context "when casks are not installed", :needs_macos do
 | |
|     it "raises an error" do
 | |
|       allow(Homebrew::Bundle).to receive(:cask_installed?).and_return(true)
 | |
|       allow(Homebrew::Bundle::CaskDumper).to receive(:casks).and_return([])
 | |
|       allow(Homebrew::Bundle::FormulaInstaller).to receive(:upgradable_formulae).and_return([])
 | |
|       allow_any_instance_of(Pathname).to receive(:read).and_return("cask 'abc'")
 | |
|       expect { do_check }.to raise_error(SystemExit)
 | |
|     end
 | |
|   end
 | |
| 
 | |
|   context "when formulae are not installed" do
 | |
|     let(:verbose) { true }
 | |
| 
 | |
|     it "raises an error and outputs to stdout" do
 | |
|       allow(Homebrew::Bundle::CaskDumper).to receive(:casks).and_return([])
 | |
|       allow(Homebrew::Bundle::FormulaInstaller).to receive(:upgradable_formulae).and_return([])
 | |
|       allow_any_instance_of(Pathname).to receive(:read).and_return("brew 'abc'")
 | |
|       expect { do_check }.to raise_error(SystemExit).and \
 | |
|         output(/brew bundle can't satisfy your Brewfile's dependencies/).to_stdout
 | |
|     end
 | |
| 
 | |
|     it "partially outputs when HOMEBREW_BUNDLE_CHECK_ALREADY_OUTPUT_FORMULAE_ERRORS is set" do
 | |
|       allow(Homebrew::Bundle::CaskDumper).to receive(:casks).and_return([])
 | |
|       allow(Homebrew::Bundle::FormulaInstaller).to receive(:upgradable_formulae).and_return([])
 | |
|       allow_any_instance_of(Pathname).to receive(:read).and_return("brew 'abc'")
 | |
|       ENV["HOMEBREW_BUNDLE_CHECK_ALREADY_OUTPUT_FORMULAE_ERRORS"] = "abc"
 | |
|       expect { do_check }.to raise_error(SystemExit).and \
 | |
|         output("Satisfy missing dependencies with `brew bundle install`.\n").to_stdout
 | |
|     end
 | |
| 
 | |
|     it "does not raise error on skippable formula" do
 | |
|       allow(Homebrew::Bundle::CaskDumper).to receive(:casks).and_return([])
 | |
|       allow(Homebrew::Bundle::FormulaInstaller).to receive(:upgradable_formulae).and_return([])
 | |
|       allow(Homebrew::Bundle::Skipper).to receive(:skip?).and_return(true)
 | |
|       allow_any_instance_of(Pathname).to receive(:read).and_return("brew 'abc'")
 | |
|       expect { do_check }.not_to raise_error
 | |
|     end
 | |
|   end
 | |
| 
 | |
|   context "when taps are not tapped" do
 | |
|     it "raises an error" do
 | |
|       allow(Homebrew::Bundle::CaskDumper).to receive(:casks).and_return([])
 | |
|       allow(Homebrew::Bundle::FormulaInstaller).to receive(:upgradable_formulae).and_return([])
 | |
|       allow_any_instance_of(Pathname).to receive(:read).and_return("tap 'abc/def'")
 | |
|       expect { do_check }.to raise_error(SystemExit)
 | |
|     end
 | |
|   end
 | |
| 
 | |
|   context "when apps are not installed", :needs_macos do
 | |
|     it "raises an error" do
 | |
|       allow(Homebrew::Bundle::MacAppStoreDumper).to receive(:app_ids).and_return([])
 | |
|       allow(Homebrew::Bundle::FormulaInstaller).to receive(:upgradable_formulae).and_return([])
 | |
|       allow_any_instance_of(Pathname).to receive(:read).and_return("mas 'foo', id: 123")
 | |
|       expect { do_check }.to raise_error(SystemExit)
 | |
|     end
 | |
|   end
 | |
| 
 | |
|   context "when service is not started and app not installed" do
 | |
|     let(:verbose) { true }
 | |
|     let(:expected_output) do
 | |
|       <<~MSG
 | |
|         brew bundle can't satisfy your Brewfile's dependencies.
 | |
|         → App foo needs to be installed or updated.
 | |
|         → Service def needs to be started.
 | |
|         Satisfy missing dependencies with `brew bundle install`.
 | |
|       MSG
 | |
|     end
 | |
| 
 | |
|     before do
 | |
|       Homebrew::Bundle::Checker.reset!
 | |
|       allow_any_instance_of(Homebrew::Bundle::Checker::MacAppStoreChecker).to \
 | |
|         receive(:installed_and_up_to_date?).and_return(false)
 | |
|       allow(Homebrew::Bundle::FormulaInstaller).to receive_messages(installed_formulae:  ["abc", "def"],
 | |
|                                                                     upgradable_formulae: [])
 | |
|       allow(Homebrew::Bundle::BrewServices).to receive(:started?).with("abc").and_return(true)
 | |
|       allow(Homebrew::Bundle::BrewServices).to receive(:started?).with("def").and_return(false)
 | |
|     end
 | |
| 
 | |
|     it "does not raise error when no service needs to be started" do
 | |
|       Homebrew::Bundle::Checker.reset!
 | |
|       allow_any_instance_of(Pathname).to receive(:read).and_return("brew 'abc'")
 | |
| 
 | |
|       expect(Homebrew::Bundle::FormulaInstaller.installed_formulae).to include("abc")
 | |
|       expect(Homebrew::Bundle::CaskInstaller.installed_casks).not_to include("abc")
 | |
|       expect(Homebrew::Bundle::BrewServices.started?("abc")).to be(true)
 | |
| 
 | |
|       expect { do_check }.not_to raise_error
 | |
|     end
 | |
| 
 | |
|     context "when restart_service is true" do
 | |
|       it "raises an error" do
 | |
|         allow_any_instance_of(Pathname)
 | |
|           .to receive(:read).and_return("brew 'abc', restart_service: true\nbrew 'def', restart_service: true")
 | |
|         allow_any_instance_of(Homebrew::Bundle::Checker::MacAppStoreChecker)
 | |
|           .to receive(:format_checkable).and_return(1 => "foo")
 | |
|         expect { do_check }.to raise_error(SystemExit).and output(expected_output).to_stdout
 | |
|       end
 | |
|     end
 | |
| 
 | |
|     context "when start_service is true" do
 | |
|       it "raises an error" do
 | |
|         allow_any_instance_of(Pathname)
 | |
|           .to receive(:read).and_return("brew 'abc', start_service: true\nbrew 'def', start_service: true")
 | |
|         allow_any_instance_of(Homebrew::Bundle::Checker::MacAppStoreChecker)
 | |
|           .to receive(:format_checkable).and_return(1 => "foo")
 | |
|         expect { do_check }.to raise_error(SystemExit).and output(expected_output).to_stdout
 | |
|       end
 | |
|     end
 | |
|   end
 | |
| 
 | |
|   context "when app not installed and `no_upgrade` is true" do
 | |
|     let(:expected_output) do
 | |
|       <<~MSG
 | |
|         brew bundle can't satisfy your Brewfile's dependencies.
 | |
|         → App foo needs to be installed.
 | |
|         Satisfy missing dependencies with `brew bundle install`.
 | |
|       MSG
 | |
|     end
 | |
|     let(:no_upgrade) { true }
 | |
|     let(:verbose) { true }
 | |
| 
 | |
|     before do
 | |
|       Homebrew::Bundle::Checker.reset!
 | |
|       allow_any_instance_of(Homebrew::Bundle::Checker::MacAppStoreChecker).to \
 | |
|         receive(:installed_and_up_to_date?).and_return(false)
 | |
|       allow(Homebrew::Bundle::FormulaInstaller).to receive(:installed_formulae).and_return(["abc", "def"])
 | |
|     end
 | |
| 
 | |
|     it "raises an error that doesn't mention upgrade" do
 | |
|       allow_any_instance_of(Pathname).to receive(:read).and_return("brew 'abc'")
 | |
|       allow_any_instance_of(Homebrew::Bundle::Checker::MacAppStoreChecker).to \
 | |
|         receive(:format_checkable).and_return(1 => "foo")
 | |
|       expect { do_check }.to raise_error(SystemExit).and output(expected_output).to_stdout
 | |
|     end
 | |
|   end
 | |
| 
 | |
|   context "when extension not installed" do
 | |
|     let(:expected_output) do
 | |
|       <<~MSG
 | |
|         brew bundle can't satisfy your Brewfile's dependencies.
 | |
|         → VSCode Extension foo needs to be installed.
 | |
|         Satisfy missing dependencies with `brew bundle install`.
 | |
|       MSG
 | |
|     end
 | |
|     let(:verbose) { true }
 | |
| 
 | |
|     before do
 | |
|       Homebrew::Bundle::Checker.reset!
 | |
|       allow_any_instance_of(Homebrew::Bundle::Checker::VscodeExtensionChecker).to \
 | |
|         receive(:installed_and_up_to_date?).and_return(false)
 | |
|     end
 | |
| 
 | |
|     it "raises an error that doesn't mention upgrade" do
 | |
|       allow_any_instance_of(Pathname).to receive(:read).and_return("vscode 'foo'")
 | |
|       expect { do_check }.to raise_error(SystemExit).and output(expected_output).to_stdout
 | |
|     end
 | |
|   end
 | |
| 
 | |
|   context "when there are taps to install" do
 | |
|     before do
 | |
|       allow_any_instance_of(Pathname).to receive(:read).and_return("")
 | |
|       allow(Homebrew::Bundle::Checker).to receive(:taps_to_tap).and_return(["asdf"])
 | |
|     end
 | |
| 
 | |
|     it "does not check for casks" do
 | |
|       expect(Homebrew::Bundle::Checker).not_to receive(:casks_to_install)
 | |
|       expect { do_check }.to raise_error(SystemExit)
 | |
|     end
 | |
| 
 | |
|     it "does not check for formulae" do
 | |
|       expect(Homebrew::Bundle::Checker).not_to receive(:formulae_to_install)
 | |
|       expect { do_check }.to raise_error(SystemExit)
 | |
|     end
 | |
| 
 | |
|     it "does not check for apps" do
 | |
|       expect(Homebrew::Bundle::Checker).not_to receive(:apps_to_install)
 | |
|       expect { do_check }.to raise_error(SystemExit)
 | |
|     end
 | |
|   end
 | |
| 
 | |
|   context "when there are VSCode extensions to install" do
 | |
|     before do
 | |
|       allow_any_instance_of(Pathname).to receive(:read).and_return("")
 | |
|       allow(Homebrew::Bundle::Checker).to receive(:extensions_to_install).and_return(["asdf"])
 | |
|     end
 | |
| 
 | |
|     it "does not check for formulae" do
 | |
|       expect(Homebrew::Bundle::Checker).not_to receive(:formulae_to_install)
 | |
|       expect { do_check }.to raise_error(SystemExit)
 | |
|     end
 | |
| 
 | |
|     it "does not check for apps" do
 | |
|       expect(Homebrew::Bundle::Checker).not_to receive(:apps_to_install)
 | |
|       expect { do_check }.to raise_error(SystemExit)
 | |
|     end
 | |
|   end
 | |
| 
 | |
|   context "when there are formulae to install" do
 | |
|     before do
 | |
|       allow_any_instance_of(Pathname).to receive(:read).and_return("")
 | |
|       allow(Homebrew::Bundle::Checker).to \
 | |
|         receive_messages(taps_to_tap:         [],
 | |
|                          casks_to_install:    [],
 | |
|                          apps_to_install:     [],
 | |
|                          formulae_to_install: ["one"])
 | |
|     end
 | |
| 
 | |
|     it "does not start formulae" do
 | |
|       expect(Homebrew::Bundle::Checker).not_to receive(:formulae_to_start)
 | |
|       expect { do_check }.to raise_error(SystemExit)
 | |
|     end
 | |
|   end
 | |
| 
 | |
|   context "when verbose mode is not enabled" do
 | |
|     it "stops checking after the first missing formula" do
 | |
|       allow(Homebrew::Bundle::CaskDumper).to receive(:casks).and_return([])
 | |
|       allow(Homebrew::Bundle::FormulaInstaller).to receive(:upgradable_formulae).and_return([])
 | |
|       allow_any_instance_of(Pathname).to receive(:read).and_return("brew 'abc'\nbrew 'def'")
 | |
| 
 | |
|       expect_any_instance_of(Homebrew::Bundle::Checker::BrewChecker).to \
 | |
|         receive(:exit_early_check).once.and_call_original
 | |
|       expect { do_check }.to raise_error(SystemExit)
 | |
|     end
 | |
| 
 | |
|     it "stops checking after the first missing cask", :needs_macos do
 | |
|       allow_any_instance_of(Pathname).to receive(:read).and_return("cask 'abc'\ncask 'def'")
 | |
| 
 | |
|       expect_any_instance_of(Homebrew::Bundle::Checker::CaskChecker).to \
 | |
|         receive(:exit_early_check).once.and_call_original
 | |
|       expect { do_check }.to raise_error(SystemExit)
 | |
|     end
 | |
| 
 | |
|     it "stops checking after the first missing mac app", :needs_macos do
 | |
|       allow_any_instance_of(Pathname).to receive(:read).and_return("mas 'foo', id: 123\nmas 'bar', id: 456")
 | |
| 
 | |
|       expect_any_instance_of(Homebrew::Bundle::Checker::MacAppStoreChecker).to \
 | |
|         receive(:exit_early_check).once.and_call_original
 | |
|       expect { do_check }.to raise_error(SystemExit)
 | |
|     end
 | |
| 
 | |
|     it "stops checking after the first VSCode extension" do
 | |
|       allow_any_instance_of(Pathname).to receive(:read).and_return("vscode 'abc'\nvscode 'def'")
 | |
| 
 | |
|       expect_any_instance_of(Homebrew::Bundle::Checker::VscodeExtensionChecker).to \
 | |
|         receive(:exit_early_check).once.and_call_original
 | |
|       expect { do_check }.to raise_error(SystemExit)
 | |
|     end
 | |
|   end
 | |
| 
 | |
|   context "when a new checker fails to implement installed_and_up_to_date" do
 | |
|     it "raises an exception" do
 | |
|       stub_const("TestChecker", Class.new(Homebrew::Bundle::Checker::Base) do
 | |
|         class_eval("PACKAGE_TYPE = :test", __FILE__, __LINE__)
 | |
|       end.freeze)
 | |
| 
 | |
|       test_entry = Homebrew::Bundle::Dsl::Entry.new(:test, "test")
 | |
|       expect { TestChecker.new.find_actionable([test_entry]) }.to raise_error(NotImplementedError)
 | |
|     end
 | |
|   end
 | |
| end
 |