Port Homebrew::DevCmd::Test

This commit is contained in:
Douglas Eichelberger 2024-03-21 21:52:32 -07:00
parent e0519d736a
commit 827e943803
3 changed files with 102 additions and 100 deletions

View File

@ -1,126 +1,127 @@
# typed: true
# frozen_string_literal: true
require "abstract_command"
require "extend/ENV"
require "sandbox"
require "timeout"
require "cli/parser"
module Homebrew
module_function
module DevCmd
class Test < AbstractCommand
cmd_args do
description <<~EOS
Run the test method provided by an installed formula.
There is no standard output or return code, but generally it should notify the
user if something is wrong with the installed formula.
sig { returns(CLI::Parser) }
def test_args
Homebrew::CLI::Parser.new do
description <<~EOS
Run the test method provided by an installed formula.
There is no standard output or return code, but generally it should notify the
user if something is wrong with the installed formula.
*Example:* `brew install jruby && brew test jruby`
EOS
switch "-f", "--force",
description: "Test formulae even if they are unlinked."
switch "--HEAD",
description: "Test the HEAD version of a formula."
switch "--keep-tmp",
description: "Retain the temporary files created for the test."
switch "--retry",
description: "Retry if a testing fails."
*Example:* `brew install jruby && brew test jruby`
EOS
switch "-f", "--force",
description: "Test formulae even if they are unlinked."
switch "--HEAD",
description: "Test the HEAD version of a formula."
switch "--keep-tmp",
description: "Retain the temporary files created for the test."
switch "--retry",
description: "Retry if a testing fails."
named_args :installed_formula, min: 1, without_api: true
end
end
def test
args = test_args.parse
Homebrew.install_bundler_gems!(groups: ["formula_test"], setup_path: false)
require "formula_assertions"
require "formula_free_port"
args.named.to_resolved_formulae.each do |f|
# Cannot test uninstalled formulae
unless f.latest_version_installed?
ofail "Testing requires the latest version of #{f.full_name}"
next
named_args :installed_formula, min: 1, without_api: true
end
# Cannot test formulae without a test method
unless f.test_defined?
ofail "#{f.full_name} defines no test"
next
end
sig { override.void }
def run
Homebrew.install_bundler_gems!(groups: ["formula_test"], setup_path: false)
# Don't test unlinked formulae
if !args.force? && !f.keg_only? && !f.linked?
ofail "#{f.full_name} is not linked"
next
end
require "formula_assertions"
require "formula_free_port"
# Don't test formulae missing test dependencies
missing_test_deps = f.recursive_dependencies do |dependent, dependency|
Dependency.prune if dependency.installed?
next if dependency.test? && dependent == f
args.named.to_resolved_formulae.each do |f|
# Cannot test uninstalled formulae
unless f.latest_version_installed?
ofail "Testing requires the latest version of #{f.full_name}"
next
end
Dependency.prune unless dependency.required?
end.map(&:to_s)
unless missing_test_deps.empty?
ofail "#{f.full_name} is missing test dependencies: #{missing_test_deps.join(" ")}"
next
end
# Cannot test formulae without a test method
unless f.test_defined?
ofail "#{f.full_name} defines no test"
next
end
oh1 "Testing #{f.full_name}"
# Don't test unlinked formulae
if !args.force? && !f.keg_only? && !f.linked?
ofail "#{f.full_name} is not linked"
next
end
env = ENV.to_hash
# Don't test formulae missing test dependencies
missing_test_deps = f.recursive_dependencies do |dependent, dependency|
Dependency.prune if dependency.installed?
next if dependency.test? && dependent == f
begin
exec_args = HOMEBREW_RUBY_EXEC_ARGS + %W[
--
#{HOMEBREW_LIBRARY_PATH}/test.rb
#{f.path}
].concat(args.options_only)
Dependency.prune unless dependency.required?
end.map(&:to_s)
unless missing_test_deps.empty?
ofail "#{f.full_name} is missing test dependencies: #{missing_test_deps.join(" ")}"
next
end
exec_args << "--HEAD" if f.head?
oh1 "Testing #{f.full_name}"
Utils.safe_fork do
if Sandbox.available?
sandbox = Sandbox.new
f.logs.mkpath
sandbox.record_log(f.logs/"test.sandbox.log")
sandbox.allow_write_temp_and_cache
sandbox.allow_write_log(f)
sandbox.allow_write_xcode
sandbox.allow_write_path(HOMEBREW_PREFIX/"var/cache")
sandbox.allow_write_path(HOMEBREW_PREFIX/"var/homebrew/locks")
sandbox.allow_write_path(HOMEBREW_PREFIX/"var/log")
sandbox.allow_write_path(HOMEBREW_PREFIX/"var/run")
sandbox.exec(*exec_args)
else
exec(*exec_args)
env = ENV.to_hash
begin
exec_args = HOMEBREW_RUBY_EXEC_ARGS + %W[
--
#{HOMEBREW_LIBRARY_PATH}/test.rb
#{f.path}
].concat(args.options_only)
exec_args << "--HEAD" if f.head?
Utils.safe_fork do
if Sandbox.available?
sandbox = Sandbox.new
f.logs.mkpath
sandbox.record_log(f.logs/"test.sandbox.log")
sandbox.allow_write_temp_and_cache
sandbox.allow_write_log(f)
sandbox.allow_write_xcode
sandbox.allow_write_path(HOMEBREW_PREFIX/"var/cache")
sandbox.allow_write_path(HOMEBREW_PREFIX/"var/homebrew/locks")
sandbox.allow_write_path(HOMEBREW_PREFIX/"var/log")
sandbox.allow_write_path(HOMEBREW_PREFIX/"var/run")
sandbox.exec(*exec_args)
else
exec(*exec_args)
end
end
rescue Exception => e # rubocop:disable Lint/RescueException
retry if retry_test?(f, args:)
ofail "#{f.full_name}: failed"
$stderr.puts e, Utils::Backtrace.clean(e)
ensure
ENV.replace(env)
end
end
rescue Exception => e # rubocop:disable Lint/RescueException
retry if retry_test?(f, args:)
ofail "#{f.full_name}: failed"
$stderr.puts e, Utils::Backtrace.clean(e)
ensure
ENV.replace(env)
end
end
end
def retry_test?(formula, args:)
@test_failed ||= Set.new
if args.retry? && @test_failed.add?(formula)
oh1 "Testing #{formula.full_name} (again)"
formula.clear_cache
ENV["RUST_BACKTRACE"] = "full"
true
else
Homebrew.failed = true
false
private
def retry_test?(formula, args:)
@test_failed ||= Set.new
if args.retry? && @test_failed.add?(formula)
oh1 "Testing #{formula.full_name} (again)"
formula.clear_cache
ENV["RUST_BACKTRACE"] = "full"
true
else
Homebrew.failed = true
false
end
end
end
end
end

View File

@ -18,7 +18,7 @@ require "dev-cmd/test"
TEST_TIMEOUT_SECONDS = 5 * 60
begin
args = Homebrew.test_args.parse
args = Homebrew::DevCmd::Test.new.args
Context.current = args.context
error_pipe = UNIXSocket.open(ENV.fetch("HOMEBREW_ERROR_PIPE"), &:recv_io)

View File

@ -1,8 +1,9 @@
# frozen_string_literal: true
require "cmd/shared_examples/args_parse"
require "dev-cmd/test"
RSpec.describe "brew test" do
RSpec.describe Homebrew::DevCmd::Test do
it_behaves_like "parseable arguments"
it "tests a given Formula", :integration_test do