| 
									
										
										
										
											2025-03-18 17:38:37 +00:00
										 |  |  | # frozen_string_literal: true | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | require "bundle" | 
					
						
							| 
									
										
										
										
											2025-03-24 21:55:47 +08:00
										 |  |  | require "bundle/commands/exec" | 
					
						
							| 
									
										
										
										
											2025-04-02 07:30:14 +01:00
										 |  |  | require "bundle/brewfile" | 
					
						
							|  |  |  | require "bundle/brew_services" | 
					
						
							| 
									
										
										
										
											2025-03-18 17:38:37 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | RSpec.describe Homebrew::Bundle::Commands::Exec do | 
					
						
							|  |  |  |   context "when a Brewfile is not found" do | 
					
						
							|  |  |  |     it "raises an error" do | 
					
						
							|  |  |  |       expect { described_class.run }.to raise_error(RuntimeError) | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-06-10 14:37:21 +00:00
										 |  |  |   context "when a Brewfile is found", :no_api do | 
					
						
							| 
									
										
										
										
											2025-03-18 17:38:37 +00:00
										 |  |  |     let(:brewfile_contents) { "brew 'openssl'" } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     before do | 
					
						
							|  |  |  |       allow_any_instance_of(Pathname).to receive(:read) | 
					
						
							|  |  |  |         .and_return(brewfile_contents) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       # don't try to load gcc/glibc | 
					
						
							|  |  |  |       allow(DevelopmentTools).to receive_messages(needs_libc_formula?: false, needs_compiler_formula?: false) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       stub_formula_loader formula("openssl") { url "openssl-1.0" } | 
					
						
							|  |  |  |       stub_formula_loader formula("pkgconf") { url "pkgconf-1.0" } | 
					
						
							|  |  |  |       ENV.extend(Superenv) | 
					
						
							|  |  |  |       allow(ENV).to receive(:setup_build_environment) | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     context "with valid command setup" do | 
					
						
							|  |  |  |       before do | 
					
						
							|  |  |  |         allow(described_class).to receive(:exec).and_return(nil) | 
					
						
							| 
									
										
										
										
											2025-03-24 17:34:35 +00:00
										 |  |  |         Homebrew::Bundle.reset! | 
					
						
							| 
									
										
										
										
											2025-03-18 17:38:37 +00:00
										 |  |  |       end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       it "does not raise an error" do | 
					
						
							|  |  |  |         expect { described_class.run("bundle", "install") }.not_to raise_error | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       it "does not raise an error when HOMEBREW_BUNDLE_EXEC_ALL_KEG_ONLY_DEPS is set" do | 
					
						
							|  |  |  |         ENV["HOMEBREW_BUNDLE_EXEC_ALL_KEG_ONLY_DEPS"] = "1" | 
					
						
							|  |  |  |         expect { described_class.run("bundle", "install") }.not_to raise_error | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       it "uses the formula version from the environment variable" do | 
					
						
							|  |  |  |         openssl_version = "1.1.1" | 
					
						
							|  |  |  |         ENV["PATH"] = "/opt/homebrew/opt/openssl/bin:/usr/bin:/bin" | 
					
						
							|  |  |  |         ENV["MANPATH"] = "/opt/homebrew/opt/openssl/man" | 
					
						
							| 
									
										
										
										
											2025-04-01 15:35:27 +01:00
										 |  |  |         ENV["HOMEBREW_BUNDLE_FORMULA_VERSION_OPENSSL"] = openssl_version | 
					
						
							| 
									
										
										
										
											2025-03-18 17:38:37 +00:00
										 |  |  |         allow(described_class).to receive(:which).and_return(Pathname("/usr/bin/bundle")) | 
					
						
							|  |  |  |         described_class.run("bundle", "install") | 
					
						
							|  |  |  |         expect(ENV.fetch("PATH")).to include("/Cellar/openssl/1.1.1/bin") | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       it "is able to run without bundle arguments" do | 
					
						
							|  |  |  |         allow(described_class).to receive(:exec).with("bundle", "install").and_return(nil) | 
					
						
							|  |  |  |         expect { described_class.run("bundle", "install") }.not_to raise_error | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       it "raises an exception if called without a command" do | 
					
						
							|  |  |  |         expect { described_class.run }.to raise_error(RuntimeError) | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     context "with env command" do | 
					
						
							|  |  |  |       it "outputs the environment variables" do | 
					
						
							|  |  |  |         allow(OS).to receive(:linux?).and_return(true) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         expect { described_class.run("env", subcommand: "env") }.to \ | 
					
						
							| 
									
										
										
										
											2025-04-10 15:23:02 +01:00
										 |  |  |           output(/export PATH=".+:\${PATH:-}"/).to_stdout | 
					
						
							| 
									
										
										
										
											2025-03-18 17:38:37 +00:00
										 |  |  |       end | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     it "raises if called with a command that's not on the PATH" do | 
					
						
							|  |  |  |       allow(described_class).to receive_messages(exec: nil, which: nil) | 
					
						
							|  |  |  |       expect { described_class.run("bundle", "install") }.to raise_error(RuntimeError) | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     it "prepends the path of the requested command to PATH before running" do | 
					
						
							|  |  |  |       expect(described_class).to receive(:exec).with("bundle", "install").and_return(nil) | 
					
						
							| 
									
										
										
										
											2025-03-19 21:27:45 +08:00
										 |  |  |       expect(described_class).to receive(:which).twice.and_return(Pathname("/usr/local/bin/bundle")) | 
					
						
							| 
									
										
										
										
											2025-03-18 17:38:37 +00:00
										 |  |  |       allow(ENV).to receive(:prepend_path).with(any_args).and_call_original | 
					
						
							|  |  |  |       expect(ENV).to receive(:prepend_path).with("PATH", "/usr/local/bin").once.and_call_original | 
					
						
							|  |  |  |       described_class.run("bundle", "install") | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     describe "when running a command which exists but is not on the PATH" do | 
					
						
							|  |  |  |       let(:brewfile_contents) { "brew 'zlib'" } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       before do | 
					
						
							|  |  |  |         stub_formula_loader formula("zlib") { url "zlib-1.0" } | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       shared_examples "allows command execution" do |command| | 
					
						
							|  |  |  |         it "does not raise" do | 
					
						
							|  |  |  |           allow(described_class).to receive(:exec).with(command).and_return(nil) | 
					
						
							|  |  |  |           expect(described_class).not_to receive(:which) | 
					
						
							|  |  |  |           expect { described_class.run(command) }.not_to raise_error | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       it_behaves_like "allows command execution", "./configure" | 
					
						
							|  |  |  |       it_behaves_like "allows command execution", "bin/install" | 
					
						
							|  |  |  |       it_behaves_like "allows command execution", "/Users/admin/Downloads/command" | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     describe "when the Brewfile contains rbenv" do | 
					
						
							|  |  |  |       let(:rbenv_root) { Pathname.new("/tmp/.rbenv") } | 
					
						
							|  |  |  |       let(:brewfile_contents) { "brew 'rbenv'" } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       before do | 
					
						
							|  |  |  |         stub_formula_loader formula("rbenv") { url "rbenv-1.0" } | 
					
						
							|  |  |  |         ENV["HOMEBREW_RBENV_ROOT"] = rbenv_root.to_s | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       it "prepends the path of the rbenv shims to PATH before running" do | 
					
						
							|  |  |  |         allow(described_class).to receive(:exec).with("/usr/bin/true").and_return(0) | 
					
						
							|  |  |  |         allow(ENV).to receive(:fetch).with(any_args).and_call_original | 
					
						
							|  |  |  |         allow(ENV).to receive(:prepend_path).with(any_args).once.and_call_original | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         expect(ENV).to receive(:fetch).with("HOMEBREW_RBENV_ROOT", "#{Dir.home}/.rbenv").once.and_call_original | 
					
						
							|  |  |  |         expect(ENV).to receive(:prepend_path).with("PATH", rbenv_root/"shims").once.and_call_original | 
					
						
							|  |  |  |         described_class.run("/usr/bin/true") | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  |     end | 
					
						
							| 
									
										
										
										
											2025-04-02 07:30:14 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |     describe "--services" do | 
					
						
							|  |  |  |       let(:brewfile_contents) { "brew 'nginx'\nbrew 'redis'" } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       let(:nginx_formula) do | 
					
						
							|  |  |  |         instance_double( | 
					
						
							|  |  |  |           Formula, | 
					
						
							|  |  |  |           name:                     "nginx", | 
					
						
							|  |  |  |           any_version_installed?:   true, | 
					
						
							|  |  |  |           any_installed_prefix:     HOMEBREW_PREFIX/"opt/nginx", | 
					
						
							|  |  |  |           plist_name:               "homebrew.mxcl.nginx", | 
					
						
							|  |  |  |           service_name:             "nginx", | 
					
						
							|  |  |  |           versioned_formulae_names: [], | 
					
						
							| 
									
										
										
										
											2025-08-24 14:55:57 -07:00
										 |  |  |           conflicts:                [instance_double(Formula::FormulaConflict, name: "httpd")], | 
					
						
							| 
									
										
										
										
											2025-04-02 07:30:14 +01:00
										 |  |  |           keg_only?:                false, | 
					
						
							|  |  |  |         ) | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       let(:redis_formula) do | 
					
						
							|  |  |  |         instance_double( | 
					
						
							|  |  |  |           Formula, | 
					
						
							|  |  |  |           name:                     "redis", | 
					
						
							|  |  |  |           any_version_installed?:   true, | 
					
						
							|  |  |  |           any_installed_prefix:     HOMEBREW_PREFIX/"opt/redis", | 
					
						
							|  |  |  |           plist_name:               "homebrew.mxcl.redis", | 
					
						
							|  |  |  |           service_name:             "redis", | 
					
						
							|  |  |  |           versioned_formulae_names: ["redis@6.2"], | 
					
						
							|  |  |  |           conflicts:                [], | 
					
						
							|  |  |  |           keg_only?:                false, | 
					
						
							|  |  |  |         ) | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       let(:services_info_pre) do | 
					
						
							|  |  |  |         [ | 
					
						
							|  |  |  |           { "name" => "nginx", "running" => true, "loaded" => true }, | 
					
						
							|  |  |  |           { "name" => "httpd", "running" => true, "loaded" => true }, | 
					
						
							|  |  |  |           { "name" => "redis", "running" => false, "loaded" => false }, | 
					
						
							|  |  |  |           { "name" => "redis@6.2", "running" => true, "loaded" => true, "registered" => true }, | 
					
						
							|  |  |  |         ] | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       let(:services_info_post) do | 
					
						
							|  |  |  |         [ | 
					
						
							|  |  |  |           { "name" => "nginx", "running" => true, "loaded" => true }, | 
					
						
							|  |  |  |           { "name" => "httpd", "running" => false, "loaded" => false }, | 
					
						
							|  |  |  |           { "name" => "redis", "running" => true, "loaded" => true }, | 
					
						
							|  |  |  |           { "name" => "redis@6.2", "running" => false, "loaded" => false, "registered" => true }, | 
					
						
							|  |  |  |         ] | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       before do | 
					
						
							|  |  |  |         stub_formula_loader(nginx_formula, "nginx") | 
					
						
							|  |  |  |         stub_formula_loader(redis_formula, "redis") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         pkgconf = formula("pkgconf") { url "pkgconf-1.0" } | 
					
						
							|  |  |  |         stub_formula_loader(pkgconf) | 
					
						
							|  |  |  |         allow(pkgconf).to receive(:any_version_installed?).and_return(false) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         allow_any_instance_of(Pathname).to receive(:file?).and_return(true) | 
					
						
							| 
									
										
										
										
											2025-04-07 14:10:42 +01:00
										 |  |  |         allow_any_instance_of(Pathname).to receive(:realpath) { |path| path } | 
					
						
							| 
									
										
										
										
											2025-04-02 07:30:14 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |         allow(described_class).to receive(:exit!).and_return(nil) | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       shared_examples "handles service lifecycle correctly" do | 
					
						
							|  |  |  |         it "handles service lifecycle correctly" do | 
					
						
							|  |  |  |           # The order of operations is important. This unweildly looking test is so it tests that. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           # Return original service state | 
					
						
							|  |  |  |           expect(Utils).to receive(:safe_popen_read) | 
					
						
							|  |  |  |             .with(HOMEBREW_BREW_FILE, "services", "info", "--json", "nginx", "httpd", "redis", "redis@6.2") | 
					
						
							|  |  |  |             .and_return(services_info_pre.to_json) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           # Stop original nginx | 
					
						
							|  |  |  |           expect(Homebrew::Bundle::BrewServices).to receive(:stop) | 
					
						
							|  |  |  |             .with("nginx", keep: true).and_return(true).ordered | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           # Stop nginx conflicts | 
					
						
							|  |  |  |           expect(Homebrew::Bundle::BrewServices).to receive(:stop) | 
					
						
							|  |  |  |             .with("httpd", keep: true).and_return(true).ordered | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           # Start new nginx | 
					
						
							|  |  |  |           expect(Homebrew::Bundle::BrewServices).to receive(:run) | 
					
						
							|  |  |  |             .with("nginx", file: nginx_service_file).and_return(true).ordered | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           # No need to stop original redis (not started) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           # Stop redis conflicts | 
					
						
							|  |  |  |           expect(Homebrew::Bundle::BrewServices).to receive(:stop) | 
					
						
							|  |  |  |             .with("redis@6.2", keep: true).and_return(true).ordered | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           # Start new redis | 
					
						
							|  |  |  |           expect(Homebrew::Bundle::BrewServices).to receive(:run) | 
					
						
							|  |  |  |             .with("redis", file: redis_service_file).and_return(true).ordered | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           # Run exec commands | 
					
						
							|  |  |  |           expect(Kernel).to receive(:system).with("/usr/bin/true").and_return(true).ordered | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           # Return new service state | 
					
						
							|  |  |  |           expect(Utils).to receive(:safe_popen_read) | 
					
						
							|  |  |  |             .with(HOMEBREW_BREW_FILE, "services", "info", "--json", "nginx", "httpd", "redis", "redis@6.2") | 
					
						
							|  |  |  |             .and_return(services_info_post.to_json) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           # Stop new services | 
					
						
							|  |  |  |           expect(Homebrew::Bundle::BrewServices).to receive(:stop) | 
					
						
							|  |  |  |             .with("nginx", keep: true).and_return(true).ordered | 
					
						
							|  |  |  |           expect(Homebrew::Bundle::BrewServices).to receive(:stop) | 
					
						
							|  |  |  |             .with("redis", keep: true).and_return(true).ordered | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           # Restart registered services we stopped due to conflicts (skip httpd as not registered) | 
					
						
							|  |  |  |           expect(Homebrew::Bundle::BrewServices).to receive(:run).with("redis@6.2").and_return(true).ordered | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           described_class.run("/usr/bin/true", services: true) | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       context "with launchctl" do | 
					
						
							|  |  |  |         before do | 
					
						
							|  |  |  |           allow(Homebrew::Services::System).to receive(:launchctl?).and_return(true) | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         let(:nginx_service_file) { nginx_formula.any_installed_prefix/"#{nginx_formula.plist_name}.plist" } | 
					
						
							|  |  |  |         let(:redis_service_file) { redis_formula.any_installed_prefix/"#{redis_formula.plist_name}.plist" } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         include_examples "handles service lifecycle correctly" | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       context "with systemd" do | 
					
						
							|  |  |  |         before do | 
					
						
							|  |  |  |           allow(Homebrew::Services::System).to receive(:launchctl?).and_return(false) | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         let(:nginx_service_file) { nginx_formula.any_installed_prefix/"#{nginx_formula.service_name}.service" } | 
					
						
							|  |  |  |         let(:redis_service_file) { redis_formula.any_installed_prefix/"#{redis_formula.service_name}.service" } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         include_examples "handles service lifecycle correctly" | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  |     end | 
					
						
							| 
									
										
										
										
											2025-03-18 17:38:37 +00:00
										 |  |  |   end | 
					
						
							|  |  |  | end |