267 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Ruby
		
	
	
	
	
	
			
		
		
	
	
			267 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Ruby
		
	
	
	
	
	
| # frozen_string_literal: true
 | |
| 
 | |
| require "bundle"
 | |
| require "bundle/commands/exec"
 | |
| require "bundle/brewfile"
 | |
| require "bundle/brew_services"
 | |
| 
 | |
| 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
 | |
| 
 | |
|   context "when a Brewfile is found", :no_api do
 | |
|     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)
 | |
|         Homebrew::Bundle.reset!
 | |
|       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"
 | |
|         ENV["HOMEBREW_BUNDLE_FORMULA_VERSION_OPENSSL"] = openssl_version
 | |
|         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 \
 | |
|           output(/export PATH=".+:\${PATH:-}"/).to_stdout
 | |
|       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)
 | |
|       expect(described_class).to receive(:which).twice.and_return(Pathname("/usr/local/bin/bundle"))
 | |
|       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
 | |
| 
 | |
|     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: [],
 | |
|           conflicts:                [instance_double(Formula::FormulaConflict, name: "httpd")],
 | |
|           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)
 | |
|         allow_any_instance_of(Pathname).to receive(:realpath) { |path| path }
 | |
| 
 | |
|         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
 | |
|   end
 | |
| end
 | 
