bundle: add tests for exec --services

This commit is contained in:
Bo Anderson 2025-04-02 07:30:14 +01:00
parent b4ae6f5513
commit 4254b75cb8
No known key found for this signature in database
3 changed files with 189 additions and 19 deletions

View File

@ -191,13 +191,22 @@ module Homebrew
[entry, formula]
end.to_h
conflicts = entries_formulae.to_h do |entry, formula|
[
entry,
(
formula.versioned_formulae_names +
formula.conflicts.map(&:name) +
Array(entry.options[:conflicts_with])
).uniq,
]
end
# The formula + everything that could possible conflict with the service
names_to_query = entries_formulae.flat_map do |entry, formula|
[
formula.name,
*formula.versioned_formulae_names,
*formula.conflicts.map(&:name),
*entry.options[:conflicts_with],
*conflicts.fetch(entry),
]
end
@ -227,7 +236,7 @@ module Homebrew
conflicting_services = services_info.select do |candidate|
next unless candidate["running"]
formula.versioned_formulae_names.include?(candidate["name"])
conflicts.fetch(entry).include?(candidate["name"])
end
raise "Failed to get service info for #{entry.name}" if info.nil?

View File

@ -71,9 +71,11 @@ RSpec.describe Homebrew::Bundle::BrewServices do
version: "1.0",
rack: HOMEBREW_CELLAR/"fooformula",
plist_name: "homebrew.mxcl.fooformula",
service_name: "fooformula",
)
end
shared_examples "returns the versioned service file" do
it "returns the versioned service file" do
expect(Formula).to receive(:[]).with(foo.name).and_return(foo)
expect(Homebrew::Bundle).to receive(:formula_versions_from_env).and_return(foo.name => foo.version)
@ -82,12 +84,32 @@ RSpec.describe Homebrew::Bundle::BrewServices do
allow(FileTest).to receive(:directory?).and_call_original
expect(FileTest).to receive(:directory?).with(prefix.to_s).and_return(true)
service_file = prefix/"#{foo.plist_name}.plist"
expect(Homebrew::Services::System).to receive(:launchctl?).and_return(true)
service_file = prefix/service_basename
allow(FileTest).to receive(:file?).and_call_original
expect(FileTest).to receive(:file?).with(service_file.to_s).and_return(true)
expect(described_class.versioned_service_file(foo.name)).to eq(service_file)
end
end
context "with launchctl" do
before do
allow(Homebrew::Services::System).to receive(:launchctl?).and_return(true)
end
let(:service_basename) { "#{foo.plist_name}.plist" }
include_examples "returns the versioned service file"
end
context "with systemd" do
before do
allow(Homebrew::Services::System).to receive(:launchctl?).and_return(false)
end
let(:service_basename) { "#{foo.service_name}.service" }
include_examples "returns the versioned service file"
end
end
end

View File

@ -2,6 +2,8 @@
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
@ -124,5 +126,142 @@ RSpec.describe Homebrew::Bundle::Commands::Exec do
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(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(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