mirror of
https://github.com/Homebrew/brew.git
synced 2026-08-12 22:29:27 +04:00
Reapply "Enable strict typing in Homebrew::TestBot"
This reverts commit 6bab83ccc9.
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "test_bot"
|
||||
require "dev-cmd/test-bot"
|
||||
|
||||
RSpec.describe Homebrew::TestBot::Setup do
|
||||
subject(:setup) { described_class.new }
|
||||
@@ -11,7 +11,7 @@ RSpec.describe Homebrew::TestBot::Setup do
|
||||
.exactly(3).times
|
||||
.and_return(instance_double(Homebrew::TestBot::Step, passed?: true))
|
||||
|
||||
expect(setup.run!(args: instance_double(Homebrew::CLI::Args)).passed?).to be(true)
|
||||
expect(setup.run!(args: instance_double(Homebrew::Cmd::TestBotCmd::Args)).passed?).to be(true)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# typed: true # rubocop:todo Sorbet/StrictSigil
|
||||
# typed: strict
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "test_bot/step"
|
||||
@@ -23,14 +23,17 @@ module Homebrew
|
||||
|
||||
HOMEBREW_TAP_REGEX = %r{^([\w-]+)/homebrew-([\w-]+)$}
|
||||
|
||||
sig { params(args: Homebrew::Cmd::TestBotCmd::Args).returns(T::Boolean) }
|
||||
def cleanup?(args)
|
||||
args.cleanup? || GitHub::Actions.env_set?
|
||||
end
|
||||
|
||||
sig { params(args: Homebrew::Cmd::TestBotCmd::Args).returns(T::Boolean) }
|
||||
def local?(args)
|
||||
args.local? || GitHub::Actions.env_set?
|
||||
end
|
||||
|
||||
sig { params(tap: T.nilable(String)).returns(T.nilable(Tap)) }
|
||||
def resolve_test_tap(tap = nil)
|
||||
return Tap.fetch(tap) if tap
|
||||
|
||||
@@ -52,6 +55,7 @@ module Homebrew
|
||||
end
|
||||
end
|
||||
|
||||
sig { params(args: Homebrew::Cmd::TestBotCmd::Args).void }
|
||||
def run!(args)
|
||||
$stdout.sync = true
|
||||
$stderr.sync = true
|
||||
|
||||
@@ -4,8 +4,10 @@
|
||||
module Homebrew
|
||||
module TestBot
|
||||
class BottlesFetch < TestFormulae
|
||||
sig { returns(T::Array[String]) }
|
||||
attr_accessor :testing_formulae
|
||||
|
||||
sig { params(args: Homebrew::Cmd::TestBotCmd::Args).void }
|
||||
def run!(args:)
|
||||
info_header "Testing formulae:"
|
||||
puts testing_formulae
|
||||
@@ -19,6 +21,7 @@ module Homebrew
|
||||
|
||||
private
|
||||
|
||||
sig { returns(T::Hash[Symbol, T::Set[String]]) }
|
||||
def formulae_by_tag
|
||||
tags = Hash.new { |hash, key| hash[key] = Set.new }
|
||||
|
||||
@@ -38,6 +41,7 @@ module Homebrew
|
||||
tags
|
||||
end
|
||||
|
||||
sig { params(tag: Symbol, formulae: T::Set[String], args: Homebrew::Cmd::TestBotCmd::Args).void }
|
||||
def fetch_bottles!(tag, formulae, args:)
|
||||
test_header(:BottlesFetch, method: "fetch_bottles!(#{tag})")
|
||||
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
# typed: true # rubocop:todo Sorbet/StrictSigil
|
||||
# typed: strict
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Homebrew
|
||||
module TestBot
|
||||
class CleanupAfter < TestCleanup
|
||||
sig { params(args: Homebrew::Cmd::TestBotCmd::Args).void }
|
||||
def run!(args:)
|
||||
if ENV["HOMEBREW_GITHUB_ACTIONS"].present? && ENV["GITHUB_ACTIONS_HOMEBREW_SELF_HOSTED"].blank? &&
|
||||
# don't need to do post-build cleanup unless testing test-bot itself.
|
||||
@@ -27,6 +28,7 @@ module Homebrew
|
||||
|
||||
private
|
||||
|
||||
sig { void }
|
||||
def pkill_if_needed
|
||||
pgrep = ["pgrep", "-f", HOMEBREW_CELLAR.to_s]
|
||||
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
# typed: true # rubocop:todo Sorbet/StrictSigil
|
||||
# typed: strict
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Homebrew
|
||||
module TestBot
|
||||
class CleanupBefore < TestCleanup
|
||||
sig { params(args: Homebrew::Cmd::TestBotCmd::Args).void }
|
||||
def run!(args:)
|
||||
test_header(:CleanupBefore)
|
||||
|
||||
|
||||
@@ -1,21 +1,48 @@
|
||||
# typed: true # rubocop:todo Sorbet/StrictSigil
|
||||
# typed: strict
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Homebrew
|
||||
module TestBot
|
||||
class Formulae < TestFormulae
|
||||
attr_writer :testing_formulae, :added_formulae, :deleted_formulae
|
||||
sig { params(testing_formulae: T::Array[String]).void }
|
||||
attr_writer :testing_formulae
|
||||
|
||||
sig { params(added_formulae: T::Array[String]).void }
|
||||
attr_writer :added_formulae
|
||||
|
||||
sig { params(deleted_formulae: T::Array[String]).void }
|
||||
attr_writer :deleted_formulae
|
||||
|
||||
sig {
|
||||
params(
|
||||
tap: T.nilable(Tap),
|
||||
git: String,
|
||||
dry_run: T::Boolean,
|
||||
fail_fast: T::Boolean,
|
||||
verbose: T::Boolean,
|
||||
output_paths: T::Hash[Symbol, Pathname],
|
||||
).void
|
||||
}
|
||||
def initialize(tap:, git:, dry_run:, fail_fast:, verbose:, output_paths:)
|
||||
super(tap:, git:, dry_run:, fail_fast:, verbose:)
|
||||
|
||||
@built_formulae = []
|
||||
@bottle_checksums = {}
|
||||
@bottle_output_path = output_paths[:bottle]
|
||||
@linkage_output_path = output_paths[:linkage]
|
||||
@skipped_or_failed_formulae_output_path = output_paths[:skipped_or_failed_formulae]
|
||||
@built_formulae = T.let([], T::Array[String])
|
||||
@bottle_checksums = T.let({}, T::Hash[Pathname, String])
|
||||
@bottle_output_path = T.let(output_paths.fetch(:bottle), Pathname)
|
||||
@linkage_output_path = T.let(output_paths.fetch(:linkage), Pathname)
|
||||
@skipped_or_failed_formulae_output_path = T.let(output_paths.fetch(:skipped_or_failed_formulae), Pathname)
|
||||
@testing_formulae = T.let([], T::Array[String])
|
||||
@added_formulae = T.let([], T::Array[String])
|
||||
@deleted_formulae = T.let([], T::Array[String])
|
||||
@testing_formulae_count = T.let(0, Integer)
|
||||
@tested_formulae_count = T.let(0, Integer)
|
||||
@unchanged_dependencies = T.let([], T::Array[String])
|
||||
@unchanged_build_dependencies = T.let([], T::Array[String])
|
||||
@bottle_filename = T.let(nil, T.nilable(Pathname))
|
||||
@bottle_json_filename = T.let(nil, T.nilable(Pathname))
|
||||
end
|
||||
|
||||
sig { params(args: Homebrew::Cmd::TestBotCmd::Args).void }
|
||||
def run!(args:)
|
||||
test_header(:Formulae)
|
||||
|
||||
@@ -87,6 +114,7 @@ module Homebrew
|
||||
|
||||
private
|
||||
|
||||
sig { params(deps: T::Array[Dependency]).void }
|
||||
def tap_needed_taps(deps)
|
||||
deps.each { |d| d.to_formula.recursive_dependencies }
|
||||
rescue TapFormulaUnavailableError => e
|
||||
@@ -97,6 +125,7 @@ module Homebrew
|
||||
retry
|
||||
end
|
||||
|
||||
sig { void }
|
||||
def install_ca_certificates_if_needed
|
||||
return if DevelopmentTools.ca_file_handles_most_https_certificates?
|
||||
|
||||
@@ -104,6 +133,7 @@ module Homebrew
|
||||
env: { "HOMEBREW_DEVELOPER" => nil }
|
||||
end
|
||||
|
||||
sig { params(formula: Formula, formula_name: String, args: Homebrew::Cmd::TestBotCmd::Args).void }
|
||||
def setup_formulae_deps_instances(formula, formula_name, args:)
|
||||
conflicts = formula.conflicts
|
||||
formula_recursive_dependencies = formula.recursive_dependencies.map(&:to_formula)
|
||||
@@ -191,14 +221,16 @@ module Homebrew
|
||||
@unchanged_build_dependencies = build_dependencies - @testing_formulae
|
||||
end
|
||||
|
||||
sig { params(formula: Formula).void }
|
||||
def cleanup_bottle_etc_var(formula)
|
||||
# Restore etc/var files from bottle so dependents can use them.
|
||||
formula.install_etc_var
|
||||
end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def verify_local_bottles
|
||||
# Portable Ruby bottles are handled differently.
|
||||
return if testing_portable_ruby?
|
||||
return false if testing_portable_ruby?
|
||||
|
||||
# Setting `HOMEBREW_DISABLE_LOAD_FORMULA` probably doesn't do anything here but let's set it just to be safe.
|
||||
with_env(HOMEBREW_DISABLE_LOAD_FORMULA: "1") do
|
||||
@@ -245,17 +277,18 @@ module Homebrew
|
||||
end
|
||||
end
|
||||
|
||||
sig { params(formula: Formula, new_formula: T.nilable(T::Boolean), args: Homebrew::Cmd::TestBotCmd::Args).void }
|
||||
def bottle_reinstall_formula(formula, new_formula, args:)
|
||||
unless build_bottle?(formula, args:)
|
||||
@bottle_filename = nil
|
||||
@bottle_filename = T.let(nil, T.nilable(Pathname))
|
||||
return
|
||||
end
|
||||
|
||||
root_url = args.root_url
|
||||
|
||||
# GitHub Releases url
|
||||
root_url ||= if tap.present? && !tap.core_tap? && !args.test_default_formula?
|
||||
"#{tap.default_remote}/releases/download/#{formula.name}-#{formula.pkg_version}"
|
||||
root_url ||= if tap.present? && !T.must(tap).core_tap? && !args.test_default_formula?
|
||||
"#{T.must(tap).default_remote}/releases/download/#{formula.name}-#{formula.pkg_version}"
|
||||
end
|
||||
|
||||
# This is needed where sparse files may be handled (bsdtar >=3.0).
|
||||
@@ -275,7 +308,7 @@ module Homebrew
|
||||
|
||||
verify_local_bottles
|
||||
test "brew", "bottle", *bottle_args
|
||||
bottle_step = steps.last
|
||||
bottle_step = steps.fetch(-1)
|
||||
|
||||
if !bottle_step.passed? || !bottle_step.output?
|
||||
failed formula.full_name, "bottling failed" unless args.dry_run?
|
||||
@@ -283,8 +316,8 @@ module Homebrew
|
||||
end
|
||||
|
||||
@bottle_filename = Pathname.new(
|
||||
bottle_step.output
|
||||
.gsub(%r{.*(\./\S+#{HOMEBREW_BOTTLES_EXTNAME_REGEX}).*}om, '\1'),
|
||||
T.must(bottle_step.output)
|
||||
.gsub(%r{.*(\./\S+#{HOMEBREW_BOTTLES_EXTNAME_REGEX}).*}om, '\1'),
|
||||
)
|
||||
@bottle_json_filename = Pathname.new(
|
||||
@bottle_filename.to_s.gsub(/\.(\d+\.)?tar\.gz$/, ".json"),
|
||||
@@ -296,7 +329,7 @@ module Homebrew
|
||||
@bottle_output_path.write(bottle_step.output, mode: "a")
|
||||
|
||||
bottle_merge_args =
|
||||
["--merge", "--write", "--no-commit", "--no-all-checks", @bottle_json_filename]
|
||||
["--merge", "--write", "--no-commit", "--no-all-checks", @bottle_json_filename.to_s]
|
||||
bottle_merge_args << "--keep-old" if args.keep_old? && !new_formula
|
||||
|
||||
test "brew", "bottle", *bottle_merge_args
|
||||
@@ -314,12 +347,13 @@ module Homebrew
|
||||
else
|
||||
ENV.fetch("HOMEBREW_VERIFY_ATTESTATIONS", nil)
|
||||
end
|
||||
test "brew", "install", "--only-dependencies", @bottle_filename,
|
||||
test "brew", "install", "--only-dependencies", @bottle_filename.to_s,
|
||||
env: { "HOMEBREW_VERIFY_ATTESTATIONS" => verify_attestations }
|
||||
test "brew", "install", @bottle_filename,
|
||||
test "brew", "install", @bottle_filename.to_s,
|
||||
env: { "HOMEBREW_VERIFY_ATTESTATIONS" => verify_attestations }
|
||||
end
|
||||
|
||||
sig { params(formula: Formula, args: Homebrew::Cmd::TestBotCmd::Args).returns(T::Boolean) }
|
||||
def build_bottle?(formula, args:)
|
||||
# Build and runtime dependencies must be bottled on the current OS,
|
||||
# but accept an older compatible bottle for test dependencies.
|
||||
@@ -334,6 +368,7 @@ module Homebrew
|
||||
!args.build_from_source?
|
||||
end
|
||||
|
||||
sig { params(formula: Formula).void }
|
||||
def livecheck(formula)
|
||||
return unless formula.livecheck_defined?
|
||||
return if formula.livecheck.skip?
|
||||
@@ -344,7 +379,7 @@ module Homebrew
|
||||
return if livecheck_step.failed?
|
||||
return unless livecheck_step.output?
|
||||
|
||||
livecheck_info = JSON.parse(livecheck_step.output)&.first
|
||||
livecheck_info = JSON.parse(T.must(livecheck_step.output)).first
|
||||
|
||||
if livecheck_info["status"] == "error"
|
||||
error_msg = if livecheck_info["messages"].present? && livecheck_info["messages"].length.positive?
|
||||
@@ -395,231 +430,236 @@ module Homebrew
|
||||
end
|
||||
end
|
||||
|
||||
sig { params(formula_name: String, args: Homebrew::Cmd::TestBotCmd::Args).void }
|
||||
def formula!(formula_name, args:)
|
||||
cleanup_during!(@testing_formulae, args:)
|
||||
|
||||
test_header(:Formulae, method: "formula!(#{formula_name})")
|
||||
|
||||
formula = Formulary.factory(formula_name)
|
||||
if formula.disabled?
|
||||
skipped formula_name, "#{formula.full_name} has been disabled!"
|
||||
return
|
||||
end
|
||||
begin
|
||||
if formula.disabled?
|
||||
skipped formula_name, "#{formula.full_name} has been disabled!"
|
||||
return
|
||||
end
|
||||
|
||||
test "brew", "deps", "--tree", "--prune", "--annotate", "--include-build", "--include-test",
|
||||
named_args: formula_name
|
||||
test "brew", "deps", "--tree", "--prune", "--annotate", "--include-build", "--include-test",
|
||||
named_args: formula_name
|
||||
|
||||
deps_without_compatible_bottles = formula.deps.map(&:to_formula)
|
||||
deps_without_compatible_bottles.reject! do |dep|
|
||||
bottled_or_built?(dep, @built_formulae - @skipped_or_failed_formulae)
|
||||
end
|
||||
bottled_on_current_version = bottled?(formula, no_older_versions: true)
|
||||
deps_without_compatible_bottles = formula.deps.map(&:to_formula)
|
||||
deps_without_compatible_bottles.reject! do |dep|
|
||||
bottled_or_built?(dep, @built_formulae - @skipped_or_failed_formulae)
|
||||
end
|
||||
bottled_on_current_version = bottled?(formula, no_older_versions: true)
|
||||
|
||||
if deps_without_compatible_bottles.present? && !bottled_on_current_version
|
||||
message = <<~EOS
|
||||
#{formula_name} has dependencies without compatible bottles:
|
||||
#{deps_without_compatible_bottles * "\n "}
|
||||
EOS
|
||||
skipped formula_name, message
|
||||
return
|
||||
end
|
||||
if deps_without_compatible_bottles.present? && !bottled_on_current_version
|
||||
message = <<~EOS
|
||||
#{formula_name} has dependencies without compatible bottles:
|
||||
#{deps_without_compatible_bottles * "\n "}
|
||||
EOS
|
||||
skipped formula_name, message
|
||||
return
|
||||
end
|
||||
|
||||
new_formula = @added_formulae.include?(formula_name)
|
||||
ignore_failures = !args.test_default_formula? && !bottled_on_current_version && !new_formula
|
||||
new_formula = @added_formulae.include?(formula_name)
|
||||
ignore_failures = !args.test_default_formula? && !bottled_on_current_version && !new_formula
|
||||
|
||||
deps = []
|
||||
reqs = []
|
||||
deps = []
|
||||
reqs = []
|
||||
|
||||
build_flag = if build_bottle?(formula, args:)
|
||||
"--build-bottle"
|
||||
else
|
||||
if GitHub::Actions.env_set?
|
||||
puts GitHub::Actions::Annotation.new(
|
||||
:warning,
|
||||
"#{formula} has unbottled dependencies, so a bottle will not be built.",
|
||||
title: "No bottle built for #{formula}!",
|
||||
file: formula.path.to_s.delete_prefix("#{repository}/"),
|
||||
)
|
||||
build_flag = if build_bottle?(formula, args:)
|
||||
"--build-bottle"
|
||||
else
|
||||
onoe "Not building a bottle for #{formula} because it has unbottled dependencies."
|
||||
if GitHub::Actions.env_set?
|
||||
puts GitHub::Actions::Annotation.new(
|
||||
:warning,
|
||||
"#{formula} has unbottled dependencies, so a bottle will not be built.",
|
||||
title: "No bottle built for #{formula}!",
|
||||
file: formula.path.to_s.delete_prefix("#{repository}/"),
|
||||
)
|
||||
else
|
||||
onoe "Not building a bottle for #{formula} because it has unbottled dependencies."
|
||||
end
|
||||
|
||||
skipped formula_name, "No bottle built."
|
||||
return
|
||||
end
|
||||
|
||||
skipped formula_name, "No bottle built."
|
||||
return
|
||||
end
|
||||
# Online checks are a bit flaky and less useful for PRs that modify multiple formulae.
|
||||
skip_online_checks = args.skip_online_checks? || (@testing_formulae_count > 5)
|
||||
|
||||
# Online checks are a bit flaky and less useful for PRs that modify multiple formulae.
|
||||
skip_online_checks = args.skip_online_checks? || (@testing_formulae_count > 5)
|
||||
fetch_args = [formula_name]
|
||||
fetch_args << build_flag
|
||||
fetch_args << "--force" if cleanup?(args)
|
||||
|
||||
fetch_args = [formula_name]
|
||||
fetch_args << build_flag
|
||||
fetch_args << "--force" if cleanup?(args)
|
||||
|
||||
audit_args = [formula_name]
|
||||
audit_args << "--online" unless skip_online_checks
|
||||
if new_formula
|
||||
if !args.skip_new?
|
||||
audit_args << "--new"
|
||||
elsif !args.skip_new_strict?
|
||||
audit_args << "--strict"
|
||||
audit_args = [formula_name]
|
||||
audit_args << "--online" unless skip_online_checks
|
||||
if new_formula
|
||||
if !args.skip_new?
|
||||
audit_args << "--new"
|
||||
elsif !args.skip_new_strict?
|
||||
audit_args << "--strict"
|
||||
end
|
||||
else
|
||||
audit_args << "--git" << "--skip-style"
|
||||
audit_args << "--except=unconfirmed_checksum_change" if args.skip_checksum_only_audit?
|
||||
audit_args << "--except=stable_version" if args.skip_stable_version_audit?
|
||||
audit_args << "--except=revision" if args.skip_revision_audit?
|
||||
end
|
||||
else
|
||||
audit_args << "--git" << "--skip-style"
|
||||
audit_args << "--except=unconfirmed_checksum_change" if args.skip_checksum_only_audit?
|
||||
audit_args << "--except=stable_version" if args.skip_stable_version_audit?
|
||||
audit_args << "--except=revision" if args.skip_revision_audit?
|
||||
end
|
||||
|
||||
# This needs to be done before any network operation.
|
||||
install_ca_certificates_if_needed
|
||||
# This needs to be done before any network operation.
|
||||
install_ca_certificates_if_needed
|
||||
|
||||
if (messages = unsatisfied_requirements_messages(formula))
|
||||
test "brew", "fetch", "--formula", "--retry", *fetch_args
|
||||
test "brew", "audit", "--formula", *audit_args
|
||||
|
||||
skipped formula_name, messages
|
||||
return
|
||||
end
|
||||
|
||||
deps |= formula.deps.to_a.reject(&:optional?)
|
||||
reqs |= formula.requirements.to_a.reject(&:optional?)
|
||||
|
||||
tap_needed_taps(deps)
|
||||
install_curl_if_needed(formula)
|
||||
install_mercurial_if_needed(deps, reqs)
|
||||
install_subversion_if_needed(deps, reqs)
|
||||
setup_formulae_deps_instances(formula, formula_name, args:)
|
||||
|
||||
test "brew", "uninstall", "--formula", "--force", formula_name if formula.latest_version_installed?
|
||||
|
||||
install_args = ["--verbose", "--formula"]
|
||||
install_args << build_flag
|
||||
|
||||
# We can't verify attestations if we're building `gh`.
|
||||
verify_attestations = if formula_name == "gh"
|
||||
nil
|
||||
else
|
||||
ENV.fetch("HOMEBREW_VERIFY_ATTESTATIONS", nil)
|
||||
end
|
||||
# Don't care about e.g. bottle failures for dependencies.
|
||||
test "brew", "install", "--only-dependencies", *install_args, formula_name,
|
||||
env: { "HOMEBREW_DEVELOPER" => nil,
|
||||
"HOMEBREW_VERIFY_ATTESTATIONS" => verify_attestations }
|
||||
|
||||
info_header "Starting tests for #{formula_name}"
|
||||
|
||||
if (messages = unsatisfied_requirements_messages(formula))
|
||||
test "brew", "fetch", "--formula", "--retry", *fetch_args
|
||||
test "brew", "audit", "--formula", *audit_args
|
||||
|
||||
skipped formula_name, messages
|
||||
return
|
||||
end
|
||||
|
||||
deps |= formula.deps.to_a.reject(&:optional?)
|
||||
reqs |= formula.requirements.to_a.reject(&:optional?)
|
||||
|
||||
tap_needed_taps(deps)
|
||||
install_curl_if_needed(formula)
|
||||
install_mercurial_if_needed(deps, reqs)
|
||||
install_subversion_if_needed(deps, reqs)
|
||||
setup_formulae_deps_instances(formula, formula_name, args:)
|
||||
|
||||
test "brew", "uninstall", "--formula", "--force", formula_name if formula.latest_version_installed?
|
||||
|
||||
install_args = ["--verbose", "--formula"]
|
||||
install_args << build_flag
|
||||
|
||||
# We can't verify attestations if we're building `gh`.
|
||||
verify_attestations = if formula_name == "gh"
|
||||
nil
|
||||
else
|
||||
ENV.fetch("HOMEBREW_VERIFY_ATTESTATIONS", nil)
|
||||
end
|
||||
# Don't care about e.g. bottle failures for dependencies.
|
||||
test "brew", "install", "--only-dependencies", *install_args, formula_name,
|
||||
env: { "HOMEBREW_DEVELOPER" => nil,
|
||||
"HOMEBREW_VERIFY_ATTESTATIONS" => verify_attestations }
|
||||
|
||||
info_header "Starting tests for #{formula_name}"
|
||||
|
||||
test "brew", "fetch", "--formula", "--retry", *fetch_args
|
||||
|
||||
env = {}
|
||||
env["HOMEBREW_GIT_PATH"] = nil if deps.any? do |d|
|
||||
d.name == "git" && (!d.test? || d.build?)
|
||||
end
|
||||
|
||||
install_step_passed = formula_installed_from_bottle =
|
||||
artifact_cache_valid?(formula) &&
|
||||
verify_local_bottles && # Checking the artifact cache loads formulae, so do this check second.
|
||||
install_formula_from_bottle!(formula_name,
|
||||
bottle_dir: artifact_cache,
|
||||
testing_formulae_dependents: false,
|
||||
dry_run: args.dry_run?)
|
||||
|
||||
install_step_passed ||= begin
|
||||
test("brew", "install", *install_args,
|
||||
named_args: formula_name,
|
||||
env: env.merge({ "HOMEBREW_DEVELOPER" => nil,
|
||||
"HOMEBREW_VERIFY_ATTESTATIONS" => verify_attestations }),
|
||||
ignore_failures:, report_analytics: true)
|
||||
steps.last.passed?
|
||||
end
|
||||
|
||||
livecheck(formula) if !args.skip_livecheck? && !skip_online_checks
|
||||
|
||||
test "brew", "style", "--formula", formula_name, report_analytics: true
|
||||
test "brew", "audit", "--formula", *audit_args, report_analytics: true unless formula.deprecated?
|
||||
unless install_step_passed
|
||||
if ignore_failures
|
||||
skipped formula_name, "install failed"
|
||||
else
|
||||
failed formula_name, "install failed"
|
||||
end
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
if formula_installed_from_bottle
|
||||
moved_artifacts = bottle_glob(formula_name, artifact_cache, ".{json,tar.gz}").map(&:realpath)
|
||||
Pathname.pwd.install moved_artifacts
|
||||
|
||||
moved_artifacts.each do |old_location|
|
||||
new_location = old_location.basename.realpath
|
||||
@bottle_checksums[new_location] = @bottle_checksums.fetch(old_location)
|
||||
@bottle_checksums.delete(old_location)
|
||||
end
|
||||
else
|
||||
bottle_reinstall_formula(formula, new_formula, args:)
|
||||
end
|
||||
@built_formulae << formula.full_name
|
||||
test("brew", "linkage", "--test", named_args: formula_name, ignore_failures:, report_analytics: true)
|
||||
failed_linkage_or_test_messages ||= []
|
||||
failed_linkage_or_test_messages << "linkage failed" unless steps.last.passed?
|
||||
|
||||
if steps.last.passed?
|
||||
# Check for opportunistic linkage. Ignore failures because
|
||||
# they can be unavoidable but we still want to know about them.
|
||||
test "brew", "linkage", "--cached", "--test", "--strict",
|
||||
named_args: formula_name,
|
||||
ignore_failures: !args.test_default_formula?
|
||||
end
|
||||
|
||||
test "brew", "linkage", "--cached", formula_name
|
||||
@linkage_output_path.write(Formatter.headline(steps.last.command_trimmed, color: :blue), mode: "a")
|
||||
@linkage_output_path.write("\n", mode: "a")
|
||||
@linkage_output_path.write(steps.last.output, mode: "a")
|
||||
|
||||
test "brew", "install", "--formula", "--only-dependencies", "--include-test", formula_name
|
||||
|
||||
if formula.test_defined?
|
||||
env = {}
|
||||
env["HOMEBREW_GIT_PATH"] = nil if deps.any? do |d|
|
||||
d.name == "git" && (!d.build? || d.test?)
|
||||
d.name == "git" && (!d.test? || d.build?)
|
||||
end
|
||||
|
||||
# Intentionally not passing --retry here to avoid papering over
|
||||
# flaky tests when a formula isn't being pulled in as a dependent.
|
||||
test("brew", "test", "--verbose", named_args: formula_name, env:, ignore_failures:,
|
||||
report_analytics: true)
|
||||
failed_linkage_or_test_messages << "test failed" unless steps.last.passed?
|
||||
end
|
||||
install_step_passed = formula_installed_from_bottle =
|
||||
artifact_cache_valid?(formula) &&
|
||||
verify_local_bottles && # Checking the artifact cache loads formulae, so do this check second.
|
||||
install_formula_from_bottle!(formula_name,
|
||||
bottle_dir: artifact_cache,
|
||||
testing_formulae_dependents: false,
|
||||
dry_run: args.dry_run?)
|
||||
|
||||
# Move bottle and don't test dependents if the formula linkage or test failed.
|
||||
if failed_linkage_or_test_messages.present?
|
||||
if @bottle_filename
|
||||
failed_dir = @bottle_filename.dirname/"failed"
|
||||
moved_artifacts = [@bottle_filename, @bottle_json_filename].map(&:realpath)
|
||||
failed_dir.install moved_artifacts
|
||||
install_step_passed ||= begin
|
||||
test("brew", "install", *install_args,
|
||||
named_args: formula_name,
|
||||
env: env.merge({ "HOMEBREW_DEVELOPER" => nil,
|
||||
"HOMEBREW_VERIFY_ATTESTATIONS" => verify_attestations }),
|
||||
ignore_failures:, report_analytics: true)
|
||||
steps.fetch(-1).passed?
|
||||
end
|
||||
|
||||
livecheck(formula) if !args.skip_livecheck? && !skip_online_checks
|
||||
|
||||
test "brew", "style", "--formula", formula_name, report_analytics: true
|
||||
test "brew", "audit", "--formula", *audit_args, report_analytics: true unless formula.deprecated?
|
||||
unless install_step_passed
|
||||
if ignore_failures
|
||||
skipped formula_name, "install failed"
|
||||
else
|
||||
failed formula_name, "install failed"
|
||||
end
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
if formula_installed_from_bottle
|
||||
moved_artifacts = bottle_glob(formula_name, artifact_cache, ".{json,tar.gz}").map(&:realpath)
|
||||
Pathname.pwd.install moved_artifacts
|
||||
|
||||
moved_artifacts.each do |old_location|
|
||||
new_location = (failed_dir/old_location.basename).realpath
|
||||
new_location = old_location.basename.realpath
|
||||
@bottle_checksums[new_location] = @bottle_checksums.fetch(old_location)
|
||||
@bottle_checksums.delete(old_location)
|
||||
end
|
||||
end
|
||||
|
||||
if ignore_failures
|
||||
skipped formula_name, failed_linkage_or_test_messages.join(", ")
|
||||
else
|
||||
failed formula_name, failed_linkage_or_test_messages.join(", ")
|
||||
bottle_reinstall_formula(formula, new_formula, args:)
|
||||
end
|
||||
end
|
||||
ensure
|
||||
@tested_formulae_count += 1
|
||||
cleanup_bottle_etc_var(formula) if cleanup?(args)
|
||||
@built_formulae << formula.full_name
|
||||
test("brew", "linkage", "--test", named_args: formula_name, ignore_failures:, report_analytics: true)
|
||||
failed_linkage_or_test_messages ||= []
|
||||
failed_linkage_or_test_messages << "linkage failed" unless steps.fetch(-1).passed?
|
||||
|
||||
if @unchanged_dependencies.present?
|
||||
test "brew", "uninstall", "--formulae", "--force", "--ignore-dependencies", *@unchanged_dependencies
|
||||
if steps.fetch(-1).passed?
|
||||
# Check for opportunistic linkage. Ignore failures because
|
||||
# they can be unavoidable but we still want to know about them.
|
||||
test "brew", "linkage", "--cached", "--test", "--strict",
|
||||
named_args: formula_name,
|
||||
ignore_failures: !args.test_default_formula?
|
||||
end
|
||||
|
||||
test "brew", "linkage", "--cached", formula_name
|
||||
@linkage_output_path.write(Formatter.headline(steps.fetch(-1).command_trimmed, color: :blue), mode: "a")
|
||||
@linkage_output_path.write("\n", mode: "a")
|
||||
@linkage_output_path.write(steps.fetch(-1).output, mode: "a")
|
||||
|
||||
test "brew", "install", "--formula", "--only-dependencies", "--include-test", formula_name
|
||||
|
||||
if formula.test_defined?
|
||||
env = {}
|
||||
env["HOMEBREW_GIT_PATH"] = nil if deps.any? do |d|
|
||||
d.name == "git" && (!d.build? || d.test?)
|
||||
end
|
||||
|
||||
# Intentionally not passing --retry here to avoid papering over
|
||||
# flaky tests when a formula isn't being pulled in as a dependent.
|
||||
test(
|
||||
"brew", "test", "--verbose", named_args: formula_name, env:, ignore_failures:, report_analytics: true
|
||||
)
|
||||
failed_linkage_or_test_messages << "test failed" unless steps.fetch(-1).passed?
|
||||
end
|
||||
|
||||
# Move bottle and don't test dependents if the formula linkage or test failed.
|
||||
if failed_linkage_or_test_messages.present?
|
||||
if @bottle_filename
|
||||
failed_dir = @bottle_filename.dirname/"failed"
|
||||
moved_artifacts = [@bottle_filename, T.must(@bottle_json_filename)].map(&:realpath)
|
||||
failed_dir.install moved_artifacts
|
||||
|
||||
moved_artifacts.each do |old_location|
|
||||
new_location = (failed_dir/old_location.basename).realpath
|
||||
@bottle_checksums[new_location] = @bottle_checksums.fetch(old_location)
|
||||
@bottle_checksums.delete(old_location)
|
||||
end
|
||||
end
|
||||
|
||||
if ignore_failures
|
||||
skipped formula_name, failed_linkage_or_test_messages.join(", ")
|
||||
else
|
||||
failed formula_name, failed_linkage_or_test_messages.join(", ")
|
||||
end
|
||||
end
|
||||
ensure
|
||||
@tested_formulae_count += 1
|
||||
cleanup_bottle_etc_var(formula) if cleanup?(args)
|
||||
|
||||
if @unchanged_dependencies.present?
|
||||
test "brew", "uninstall", "--formulae", "--force", "--ignore-dependencies", *@unchanged_dependencies
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
sig { params(formula_name: String).void }
|
||||
def portable_formula!(formula_name)
|
||||
test_header(:Formulae, method: "portable_formula!(#{formula_name})")
|
||||
|
||||
@@ -655,6 +695,7 @@ report_analytics: true)
|
||||
test "brew", "bottle", "--skip-relocation", "--json", "--no-rebuild", formula_name
|
||||
end
|
||||
|
||||
sig { params(formula_name: String).void }
|
||||
def deleted_formula!(formula_name)
|
||||
test_header(:Formulae, method: "deleted_formula!(#{formula_name})")
|
||||
|
||||
@@ -667,8 +708,9 @@ report_analytics: true)
|
||||
formula_name
|
||||
end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def testing_portable_ruby?
|
||||
tap&.core_tap? && @testing_formulae.include?("portable-ruby")
|
||||
!!tap&.core_tap? && @testing_formulae.include?("portable-ruby")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,11 +1,32 @@
|
||||
# typed: true # rubocop:todo Sorbet/StrictSigil
|
||||
# typed: strict
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Homebrew
|
||||
module TestBot
|
||||
class FormulaeDependents < TestFormulae
|
||||
attr_writer :testing_formulae, :tested_formulae
|
||||
sig { params(testing_formulae: T::Array[String]).returns(T::Array[String]) }
|
||||
attr_writer :testing_formulae
|
||||
|
||||
sig { params(tested_formulae: T::Array[String]).returns(T::Array[String]) }
|
||||
attr_writer :tested_formulae
|
||||
|
||||
sig {
|
||||
params(
|
||||
tap: T.nilable(Tap),
|
||||
git: T.nilable(String),
|
||||
dry_run: T::Boolean,
|
||||
fail_fast: T::Boolean,
|
||||
verbose: T::Boolean,
|
||||
).void
|
||||
}
|
||||
def initialize(tap:, git:, dry_run:, fail_fast:, verbose:)
|
||||
super
|
||||
@testing_formulae_with_tested_dependents = T.let([], T::Array[String])
|
||||
@tested_dependents_list = T.let(nil, T.nilable(Pathname))
|
||||
@dependent_testing_formulae = T.let([], T::Array[String])
|
||||
end
|
||||
|
||||
sig { params(args: Homebrew::Cmd::TestBotCmd::Args).void }
|
||||
def run!(args:)
|
||||
test "brew", "untap", "--force", "homebrew/cask" if !tap&.core_cask_tap? && CoreCaskTap.instance.installed?
|
||||
|
||||
@@ -33,11 +54,14 @@ module Homebrew
|
||||
# rubocop:enable Homebrew/MoveToExtendOS
|
||||
|
||||
download_artifacts_from_previous_run!("dependents{,_#{artifact_specifier}*}", dry_run: args.dry_run?)
|
||||
@skip_candidates = if (tested_dependents_cache = artifact_cache/@tested_dependents_list).exist?
|
||||
tested_dependents_cache.read.split("\n")
|
||||
else
|
||||
[]
|
||||
end
|
||||
@skip_candidates = T.let(
|
||||
if (tested_dependents_cache = artifact_cache/@tested_dependents_list).exist?
|
||||
tested_dependents_cache.read.split("\n")
|
||||
else
|
||||
[]
|
||||
end,
|
||||
T.nilable(T::Array[String]),
|
||||
)
|
||||
|
||||
@dependent_testing_formulae.each do |formula_name|
|
||||
dependent_formulae!(formula_name, args:)
|
||||
@@ -56,6 +80,7 @@ module Homebrew
|
||||
|
||||
private
|
||||
|
||||
sig { params(installable_bottles: T::Array[String], args: Homebrew::Cmd::TestBotCmd::Args).void }
|
||||
def install_formulae_if_needed_from_bottles!(installable_bottles, args:)
|
||||
installable_bottles.each do |formula_name|
|
||||
formula = Formulary.factory(formula_name)
|
||||
@@ -65,6 +90,7 @@ module Homebrew
|
||||
end
|
||||
end
|
||||
|
||||
sig { params(formula_name: String, args: Homebrew::Cmd::TestBotCmd::Args).void }
|
||||
def dependent_formulae!(formula_name, args:)
|
||||
cleanup_during!(@dependent_testing_formulae, args:)
|
||||
|
||||
@@ -92,13 +118,13 @@ module Homebrew
|
||||
named_args: formula_name,
|
||||
ignore_failures: !bottled?(formula, no_older_versions: true),
|
||||
env: { "HOMEBREW_DEVELOPER" => nil }
|
||||
return unless steps.last.passed?
|
||||
return unless steps.fetch(-1).passed?
|
||||
|
||||
# Restore etc/var files that may have been nuked in the build stage.
|
||||
test "brew", "postinstall",
|
||||
named_args: formula_name,
|
||||
ignore_failures: !bottled?(formula, no_older_versions: true)
|
||||
return unless steps.last.passed?
|
||||
return unless steps.fetch(-1).passed?
|
||||
|
||||
# Test texlive first to avoid GitHub-hosted runners running out of storage.
|
||||
# TODO: Try generalising this by sorting dependents according to install size,
|
||||
@@ -121,6 +147,10 @@ module Homebrew
|
||||
end
|
||||
end
|
||||
|
||||
sig {
|
||||
params(formula: Formula, formula_name: String, args: Homebrew::Cmd::TestBotCmd::Args)
|
||||
.returns([T::Array[Formula], T::Array[Formula], T::Array[Formula]])
|
||||
}
|
||||
def dependents_for_formula(formula, formula_name, args:)
|
||||
info_header "Determining dependents..."
|
||||
|
||||
@@ -224,17 +254,25 @@ module Homebrew
|
||||
[source_dependents, bottled_dependents, testable_dependents]
|
||||
end
|
||||
|
||||
sig {
|
||||
params(
|
||||
dependent: Formula,
|
||||
testable_dependents: T::Array[Formula],
|
||||
args: Homebrew::Cmd::TestBotCmd::Args,
|
||||
build_from_source: T::Boolean,
|
||||
).void
|
||||
}
|
||||
def install_dependent(dependent, testable_dependents, args:, build_from_source: false)
|
||||
if @skip_candidates.include?(dependent.full_name) &&
|
||||
if @skip_candidates&.include?(dependent.full_name) &&
|
||||
artifact_cache_valid?(dependent, formulae_dependents: true)
|
||||
@tested_dependents_list.write(dependent.full_name, mode: "a")
|
||||
@tested_dependents_list.write("\n", mode: "a")
|
||||
@tested_dependents_list&.write(dependent.full_name, mode: "a")
|
||||
@tested_dependents_list&.write("\n", mode: "a")
|
||||
skipped dependent.name, "#{dependent.full_name} has been tested at #{previous_github_sha}"
|
||||
return
|
||||
end
|
||||
|
||||
if (messages = unsatisfied_requirements_messages(dependent))
|
||||
skipped dependent, messages
|
||||
skipped dependent.name, messages
|
||||
return
|
||||
end
|
||||
|
||||
@@ -277,14 +315,14 @@ module Homebrew
|
||||
build_args << "--build-from-source"
|
||||
|
||||
test "brew", "fetch", "--build-from-source", "--retry", dependent.full_name
|
||||
return if steps.last.failed?
|
||||
return if steps.fetch(-1).failed?
|
||||
else
|
||||
fetch_formulae << dependent.full_name
|
||||
end
|
||||
|
||||
if fetch_formulae.present?
|
||||
test "brew", "fetch", "--retry", *fetch_formulae
|
||||
return if steps.last.failed?
|
||||
return if steps.fetch(-1).failed?
|
||||
end
|
||||
|
||||
unlink_conflicts dependent
|
||||
@@ -302,7 +340,7 @@ module Homebrew
|
||||
named_args: dependent.full_name,
|
||||
env: env.merge({ "HOMEBREW_DEVELOPER" => nil }),
|
||||
ignore_failures: !args.test_default_formula? && !bottled_on_current_version
|
||||
install_step = steps.last
|
||||
install_step = steps.fetch(-1)
|
||||
|
||||
return unless install_step.passed?
|
||||
end
|
||||
@@ -316,7 +354,7 @@ module Homebrew
|
||||
test "brew", "linkage", "--test",
|
||||
named_args: dependent.full_name,
|
||||
ignore_failures: !args.test_default_formula? && !bottled_on_current_version
|
||||
linkage_step = steps.last
|
||||
linkage_step = steps.fetch(-1)
|
||||
|
||||
if linkage_step.passed? && !build_from_source
|
||||
# Check for opportunistic linkage. Ignore failures because
|
||||
@@ -346,18 +384,18 @@ module Homebrew
|
||||
named_args: dependent.full_name,
|
||||
env:,
|
||||
ignore_failures: !args.test_default_formula? && !bottled_on_current_version
|
||||
test_step = steps.last
|
||||
test_step = steps.fetch(-1)
|
||||
end
|
||||
|
||||
test "brew", "uninstall", "--force", "--ignore-dependencies", dependent.full_name
|
||||
|
||||
all_tests_passed = (dependent_was_previously_installed || install_step.passed?) &&
|
||||
linkage_step.passed? &&
|
||||
(testable_dependents.exclude?(dependent) || test_step.passed?)
|
||||
(testable_dependents.exclude?(dependent) || test_step&.passed?)
|
||||
|
||||
if all_tests_passed
|
||||
@tested_dependents_list.write(dependent.full_name, mode: "a")
|
||||
@tested_dependents_list.write("\n", mode: "a")
|
||||
@tested_dependents_list&.write(dependent.full_name, mode: "a")
|
||||
@tested_dependents_list&.write("\n", mode: "a")
|
||||
end
|
||||
|
||||
return unless GitHub::Actions.env_set?
|
||||
@@ -388,6 +426,7 @@ module Homebrew
|
||||
end
|
||||
end
|
||||
|
||||
sig { params(formula: Formula).void }
|
||||
def unlink_conflicts(formula)
|
||||
return if formula.keg_only?
|
||||
return if formula.linked_keg.exist?
|
||||
|
||||
@@ -1,20 +1,33 @@
|
||||
# typed: true # rubocop:todo Sorbet/StrictSigil
|
||||
# typed: strict
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Homebrew
|
||||
module TestBot
|
||||
class FormulaeDetect < Test
|
||||
sig { returns(T::Array[String]) }
|
||||
attr_reader :testing_formulae, :added_formulae, :deleted_formulae
|
||||
|
||||
sig {
|
||||
params(
|
||||
argument: String,
|
||||
tap: T.nilable(Tap),
|
||||
git: String,
|
||||
dry_run: T::Boolean,
|
||||
fail_fast: T::Boolean,
|
||||
verbose: T::Boolean,
|
||||
).void
|
||||
}
|
||||
def initialize(argument, tap:, git:, dry_run:, fail_fast:, verbose:)
|
||||
super(tap:, git:, dry_run:, fail_fast:, verbose:)
|
||||
|
||||
@argument = argument
|
||||
@added_formulae = []
|
||||
@deleted_formulae = []
|
||||
@formulae_to_fetch = []
|
||||
@added_formulae = T.let([], T::Array[String])
|
||||
@deleted_formulae = T.let([], T::Array[String])
|
||||
@formulae_to_fetch = T.let([], T::Array[String])
|
||||
@testing_formulae = T.let([], T::Array[String])
|
||||
end
|
||||
|
||||
sig { params(args: Homebrew::Cmd::TestBotCmd::Args).void }
|
||||
def run!(args:)
|
||||
detect_formulae!(args:)
|
||||
|
||||
@@ -30,6 +43,7 @@ module Homebrew
|
||||
|
||||
private
|
||||
|
||||
sig { params(args: Homebrew::Cmd::TestBotCmd::Args).void }
|
||||
def detect_formulae!(args:)
|
||||
test_header(:FormulaeDetect, method: :detect_formulae!)
|
||||
|
||||
@@ -69,11 +83,11 @@ module Homebrew
|
||||
No known CI provider detected! If you are using GitHub Actions then we cannot find the expected environment variables! Check you have e.g. exported them to a Docker container.
|
||||
EOS
|
||||
end
|
||||
elsif tap.present? && tap.full_name.casecmp(github_repository).zero?
|
||||
elsif (tap = self.tap.presence) && tap.full_name.casecmp(github_repository)&.zero?
|
||||
# Use GitHub Actions variables for pull request jobs.
|
||||
if (base_ref = ENV.fetch("GITHUB_BASE_REF", nil)).present?
|
||||
unless tap.official?
|
||||
test git, "-C", repository, "fetch",
|
||||
test git.to_s, "-C", repository.to_s, "fetch",
|
||||
"origin", "+refs/heads/#{base_ref}"
|
||||
end
|
||||
origin_ref = "origin/#{base_ref}"
|
||||
@@ -86,7 +100,7 @@ module Homebrew
|
||||
diff_end_sha1 = github_sha
|
||||
# Use GitHub Actions variables for branch jobs.
|
||||
else
|
||||
test git, "-C", repository, "fetch", "origin", "+#{github_ref}" unless tap.official?
|
||||
test git.to_s, "-C", repository.to_s, "fetch", "origin", "+#{github_ref}" unless tap.official?
|
||||
origin_ref = "origin/#{github_ref.gsub(%r{^refs/heads/}, "")}"
|
||||
diff_end_sha1 = diff_start_sha1 = github_sha
|
||||
end
|
||||
@@ -104,7 +118,7 @@ module Homebrew
|
||||
|
||||
diff_start_sha1 = diff_end_sha1 if @testing_formulae.present?
|
||||
|
||||
if tap
|
||||
if (tap = self.tap.presence)
|
||||
tap_origin_ref_revision_args =
|
||||
[git, "-C", tap.path.to_s, "log", "-1", "--format=%h (%s)", origin_ref]
|
||||
tap_origin_ref_revision = if args.dry_run?
|
||||
@@ -129,7 +143,7 @@ module Homebrew
|
||||
|
||||
modified_formulae = []
|
||||
|
||||
if tap && diff_start_sha1 != diff_end_sha1
|
||||
if diff_start_sha1 != diff_end_sha1 && (tap = self.tap.presence)
|
||||
formula_path = tap.formula_dir.to_s
|
||||
@added_formulae +=
|
||||
diff_formulae(diff_start_sha1, diff_end_sha1, formula_path, "A")
|
||||
@@ -205,6 +219,7 @@ module Homebrew
|
||||
EOS
|
||||
end
|
||||
|
||||
sig { params(formula_name: String, args: Homebrew::Cmd::TestBotCmd::Args).returns(T.nilable(String)) }
|
||||
def safe_formula_canonical_name(formula_name, args:)
|
||||
Homebrew.with_no_api_env do
|
||||
Formulary.factory(formula_name).full_name
|
||||
@@ -213,7 +228,7 @@ module Homebrew
|
||||
raise if e.tap.installed?
|
||||
|
||||
test "brew", "tap", e.tap.name
|
||||
retry unless steps.last.failed?
|
||||
retry unless steps.fetch(-1).failed?
|
||||
onoe e
|
||||
puts e.backtrace if args.debug?
|
||||
rescue FormulaUnavailableError, TapFormulaAmbiguityError => e
|
||||
@@ -221,26 +236,36 @@ module Homebrew
|
||||
puts e.backtrace if args.debug?
|
||||
end
|
||||
|
||||
sig { params(ref: String).returns(String) }
|
||||
def rev_parse(ref)
|
||||
Utils.popen_read(git, "-C", repository, "rev-parse", "--verify", ref).strip
|
||||
end
|
||||
|
||||
sig { returns(String) }
|
||||
def current_sha1
|
||||
rev_parse("HEAD")
|
||||
end
|
||||
|
||||
sig {
|
||||
params(
|
||||
start_revision: String,
|
||||
end_revision: String,
|
||||
path: String,
|
||||
filter: String,
|
||||
).returns(T::Array[String])
|
||||
}
|
||||
def diff_formulae(start_revision, end_revision, path, filter)
|
||||
return unless tap
|
||||
raise "A tap is required to call diff_formulae" unless @tap
|
||||
|
||||
Utils.safe_popen_read(
|
||||
git, "-C", repository,
|
||||
"diff-tree", "-r", "--name-only", "--diff-filter=#{filter}",
|
||||
start_revision, end_revision, "--", path
|
||||
).lines(chomp: true).filter_map do |file|
|
||||
next unless tap.formula_file?(file)
|
||||
next unless @tap.formula_file?(file)
|
||||
|
||||
file = Pathname.new(file)
|
||||
tap.formula_file_to_name(file)
|
||||
@tap.formula_file_to_name(file)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# typed: true # rubocop:todo Sorbet/StrictSigil
|
||||
# typed: strict
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Homebrew
|
||||
@@ -6,10 +6,13 @@ module Homebrew
|
||||
# Creates Junit report with only required by BuildPulse attributes
|
||||
# See https://github.com/Homebrew/homebrew-test-bot/pull/621#discussion_r658712640
|
||||
class Junit
|
||||
sig { params(tests: T::Array[Test]).void }
|
||||
def initialize(tests)
|
||||
@tests = tests
|
||||
@xml_document = T.let(nil, T.nilable(REXML::Document))
|
||||
end
|
||||
|
||||
sig { params(filters: T.nilable(T::Array[String])).void }
|
||||
def build(filters: nil)
|
||||
filters ||= []
|
||||
|
||||
@@ -26,7 +29,7 @@ module Homebrew
|
||||
|
||||
testsuite = testsuites.add_element "testsuite"
|
||||
testsuite.add_attribute "name", "brew-test-bot.#{Utils::Bottles.tag}"
|
||||
testsuite.add_attribute "timestamp", test.steps.first.start_time.iso8601
|
||||
testsuite.add_attribute "timestamp", T.must(test.steps.fetch(0).start_time).iso8601
|
||||
|
||||
test.steps.each do |step|
|
||||
next unless filters.any? { |filter| step.command_short.start_with? filter }
|
||||
@@ -35,7 +38,7 @@ module Homebrew
|
||||
testcase.add_attribute "name", step.command_short
|
||||
testcase.add_attribute "status", step.status
|
||||
testcase.add_attribute "time", step.time
|
||||
testcase.add_attribute "timestamp", step.start_time.iso8601
|
||||
testcase.add_attribute "timestamp", T.must(step.start_time).iso8601
|
||||
|
||||
next if step.passed?
|
||||
|
||||
@@ -45,12 +48,13 @@ module Homebrew
|
||||
end
|
||||
end
|
||||
|
||||
sig { params(filename: String).void }
|
||||
def write(filename)
|
||||
output_path = Pathname(filename)
|
||||
output_path.unlink if output_path.exist?
|
||||
output_path.open("w") do |xml_file|
|
||||
pretty_print_indent = 2
|
||||
@xml_document.write(xml_file, pretty_print_indent)
|
||||
T.must(@xml_document).write(xml_file, pretty_print_indent)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
# typed: true # rubocop:todo Sorbet/StrictSigil
|
||||
# typed: strict
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Homebrew
|
||||
module TestBot
|
||||
class Setup < Test
|
||||
sig { params(args: Homebrew::Cmd::TestBotCmd::Args).returns(Step) }
|
||||
def run!(args:)
|
||||
test_header(:Setup)
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# typed: true # rubocop:todo Sorbet/StrictSigil
|
||||
# typed: strict
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "system_command"
|
||||
@@ -11,24 +11,48 @@ module Homebrew
|
||||
class Step
|
||||
include SystemCommand::Mixin
|
||||
|
||||
attr_reader :command, :name, :status, :output, :start_time, :end_time
|
||||
sig { returns(T::Array[String]) }
|
||||
attr_reader :command
|
||||
|
||||
sig { returns(T.nilable(String)) }
|
||||
attr_reader :name
|
||||
|
||||
sig { returns(Symbol) }
|
||||
attr_reader :status
|
||||
|
||||
sig { returns(T.nilable(String)) }
|
||||
attr_reader :output
|
||||
|
||||
sig { returns(T.nilable(Time)) }
|
||||
attr_reader :start_time, :end_time
|
||||
|
||||
# Instantiates a Step object.
|
||||
# @param command [Array<String>] Command to execute and arguments.
|
||||
# @param env [Hash] Environment variables to set when running command.
|
||||
# @param command Command to execute and arguments.
|
||||
# @param env Environment variables to set when running command.
|
||||
sig {
|
||||
params(
|
||||
command: T::Array[String],
|
||||
env: T::Hash[String, String],
|
||||
verbose: T::Boolean,
|
||||
named_args: T.nilable(T.any(String, T::Array[String])),
|
||||
ignore_failures: T::Boolean,
|
||||
repository: T.nilable(Pathname),
|
||||
).void
|
||||
}
|
||||
def initialize(command, env:, verbose:, named_args: nil, ignore_failures: false, repository: nil)
|
||||
@named_args = [named_args].flatten.compact.map(&:to_s)
|
||||
@command = command + @named_args
|
||||
@named_args = T.let([named_args].flatten.compact.map(&:to_s), T::Array[String])
|
||||
@command = T.let(command + @named_args, T::Array[String])
|
||||
@env = env
|
||||
@verbose = verbose
|
||||
@ignore_failures = ignore_failures
|
||||
@repository = repository
|
||||
|
||||
@name = command[1]&.delete("-")
|
||||
@status = :running
|
||||
@output = nil
|
||||
@name = T.let(command[1]&.delete("-"), T.nilable(String))
|
||||
@status = T.let(:running, Symbol)
|
||||
@output = T.let(nil, T.nilable(String))
|
||||
end
|
||||
|
||||
sig { returns(String) }
|
||||
def command_trimmed
|
||||
command.reject { |arg| arg.to_s.start_with?("--exclude") }
|
||||
.join(" ")
|
||||
@@ -37,6 +61,7 @@ module Homebrew
|
||||
.delete_prefix("/usr/bin/")
|
||||
end
|
||||
|
||||
sig { returns(String) }
|
||||
def command_short
|
||||
(@command - %W[
|
||||
brew
|
||||
@@ -56,26 +81,32 @@ module Homebrew
|
||||
.gsub(Dir.pwd, "")
|
||||
end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def passed?
|
||||
@status == :passed
|
||||
end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def failed?
|
||||
@status == :failed
|
||||
end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def ignored?
|
||||
@status == :ignored
|
||||
end
|
||||
|
||||
sig { void }
|
||||
def puts_command
|
||||
puts Formatter.headline(command_trimmed, color: :blue)
|
||||
end
|
||||
|
||||
sig { void }
|
||||
def puts_result
|
||||
puts Formatter.headline(Formatter.error("FAILED"), color: :red) unless passed?
|
||||
end
|
||||
|
||||
sig { params(message: String, title: String, file: String, line: T.nilable(Integer)).void }
|
||||
def puts_github_actions_annotation(message, title, file, line)
|
||||
return unless GitHub::Actions.env_set?
|
||||
|
||||
@@ -91,23 +122,27 @@ module Homebrew
|
||||
puts annotation
|
||||
end
|
||||
|
||||
def puts_in_github_actions_group(title)
|
||||
sig { params(title: String, _block: T.proc.void).void }
|
||||
def puts_in_github_actions_group(title, &_block)
|
||||
puts "::group::#{title}" if GitHub::Actions.env_set?
|
||||
yield
|
||||
puts "::endgroup::" if GitHub::Actions.env_set?
|
||||
end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def output?
|
||||
@output.present?
|
||||
end
|
||||
|
||||
# The execution time of the task.
|
||||
# Precondition: Step#run has been called.
|
||||
# @return [Float] execution time in seconds
|
||||
# @return execution time in seconds
|
||||
sig { returns(Float) }
|
||||
def time
|
||||
end_time - start_time
|
||||
T.must(end_time) - T.must(start_time)
|
||||
end
|
||||
|
||||
sig { void }
|
||||
def puts_full_output
|
||||
return if @output.blank? || @verbose
|
||||
|
||||
@@ -116,9 +151,10 @@ module Homebrew
|
||||
end
|
||||
end
|
||||
|
||||
sig { params(name: String).returns([Pathname, T.nilable(Integer)]) }
|
||||
def annotation_location(name)
|
||||
formula = Formulary.factory(name)
|
||||
method_sym = command.second.to_sym
|
||||
method_sym = command.fetch(1).to_sym
|
||||
method_location = formula.method(method_sym).source_location if formula.respond_to?(method_sym)
|
||||
|
||||
if method_location.present? && (method_location.first == formula.path.to_s)
|
||||
@@ -127,9 +163,10 @@ module Homebrew
|
||||
[formula.path, nil]
|
||||
end
|
||||
rescue FormulaUnavailableError
|
||||
[@repository.glob("**/#{name}*").first, nil]
|
||||
[@repository&.glob("**/#{name}*")&.first, nil]
|
||||
end
|
||||
|
||||
sig { params(output: String, max_kb: Integer, context_lines: Integer).returns(String) }
|
||||
def truncate_output(output, max_kb:, context_lines:)
|
||||
output_lines = output.lines
|
||||
first_error_index = output_lines.find_index do |line|
|
||||
@@ -152,12 +189,13 @@ module Homebrew
|
||||
else
|
||||
start = [first_error_index - context_lines, 0].max
|
||||
# Let GitHub Actions truncate us to 4KB if needed.
|
||||
output_lines[start..].join
|
||||
T.must(output_lines[start..]).join
|
||||
end
|
||||
end
|
||||
|
||||
sig { params(dry_run: T::Boolean, fail_fast: T::Boolean).void }
|
||||
def run(dry_run: false, fail_fast: false)
|
||||
@start_time = Time.now
|
||||
@start_time = T.let(Time.now, T.nilable(Time))
|
||||
|
||||
puts_command
|
||||
if dry_run
|
||||
@@ -170,12 +208,12 @@ module Homebrew
|
||||
|
||||
executable, *args = command
|
||||
|
||||
result = system_command executable, args:,
|
||||
print_stdout: @verbose,
|
||||
print_stderr: @verbose,
|
||||
env: @env
|
||||
result = system_command T.must(executable), args:,
|
||||
print_stdout: @verbose,
|
||||
print_stderr: @verbose,
|
||||
env: @env
|
||||
|
||||
@end_time = Time.now
|
||||
@end_time = T.let(Time.now, T.nilable(Time))
|
||||
|
||||
@status = if result.success?
|
||||
:passed
|
||||
|
||||
@@ -1,30 +1,32 @@
|
||||
# typed: true # rubocop:todo Sorbet/StrictSigil
|
||||
# typed: strict
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Homebrew
|
||||
module TestBot
|
||||
class TapSyntax < Test
|
||||
sig { params(args: Homebrew::Cmd::TestBotCmd::Args).void }
|
||||
def run!(args:)
|
||||
test_header(:TapSyntax)
|
||||
return unless tap.installed?
|
||||
tapped = T.must(tap)
|
||||
return unless tapped.installed?
|
||||
|
||||
unless args.stable?
|
||||
# Run `brew typecheck` if this tap is typed.
|
||||
# TODO: consider in future if we want to allow unsupported taps here.
|
||||
if tap.official? && quiet_system(git, "-C", tap.path.to_s, "grep", "-qE",
|
||||
"^# typed: (true|strict|strong)$")
|
||||
test "brew", "typecheck", tap.name
|
||||
if tapped.official? && quiet_system(git, "-C", tapped.path.to_s, "grep", "-qE",
|
||||
"^# typed: (true|strict|strong)$")
|
||||
test "brew", "typecheck", tapped.name
|
||||
end
|
||||
|
||||
test "brew", "style", tap.name
|
||||
test "brew", "style", tapped.name
|
||||
end
|
||||
|
||||
return if tap.formula_files.blank? && tap.cask_files.blank?
|
||||
return if tapped.formula_files.blank? && tapped.cask_files.blank?
|
||||
|
||||
test "brew", "readall", "--aliases", "--os=all", "--arch=all", tap.name
|
||||
test "brew", "readall", "--aliases", "--os=all", "--arch=all", tapped.name
|
||||
return if args.stable?
|
||||
|
||||
test "brew", "audit", "--except=installed", "--tap=#{tap.name}"
|
||||
test "brew", "audit", "--except=installed", "--tap=#{tapped.name}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# typed: true # rubocop:todo Sorbet/StrictSigil
|
||||
# typed: strict
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "utils/analytics"
|
||||
@@ -9,30 +9,51 @@ module Homebrew
|
||||
class Test
|
||||
include Utils::Output::Mixin
|
||||
|
||||
sig { returns(T::Array[Step]) }
|
||||
def failed_steps
|
||||
@steps.select(&:failed?)
|
||||
end
|
||||
|
||||
sig { returns(T::Array[Step]) }
|
||||
def ignored_steps
|
||||
@steps.select(&:ignored?)
|
||||
end
|
||||
|
||||
sig { returns(T::Array[Step]) }
|
||||
attr_reader :steps
|
||||
|
||||
protected
|
||||
|
||||
sig { params(args: Homebrew::Cmd::TestBotCmd::Args).returns(T::Boolean) }
|
||||
def cleanup?(args)
|
||||
Homebrew::TestBot.cleanup?(args)
|
||||
end
|
||||
|
||||
sig { params(args: Homebrew::Cmd::TestBotCmd::Args).returns(T::Boolean) }
|
||||
def local?(args)
|
||||
Homebrew::TestBot.local?(args)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :tap, :git, :repository
|
||||
sig { returns(T.nilable(Tap)) }
|
||||
attr_reader :tap
|
||||
|
||||
sig { returns(T.nilable(String)) }
|
||||
attr_reader :git
|
||||
|
||||
sig { returns(Pathname) }
|
||||
attr_reader :repository
|
||||
|
||||
sig {
|
||||
params(
|
||||
tap: T.nilable(Tap),
|
||||
git: T.nilable(String),
|
||||
dry_run: T::Boolean,
|
||||
fail_fast: T::Boolean,
|
||||
verbose: T::Boolean,
|
||||
).void
|
||||
}
|
||||
def initialize(tap: nil, git: nil, dry_run: false, fail_fast: false, verbose: false)
|
||||
@tap = tap
|
||||
@git = git
|
||||
@@ -40,24 +61,33 @@ module Homebrew
|
||||
@fail_fast = fail_fast
|
||||
@verbose = verbose
|
||||
|
||||
@steps = []
|
||||
@steps = T.let([], T::Array[Step])
|
||||
|
||||
@repository = if @tap
|
||||
@tap.path
|
||||
else
|
||||
CoreTap.instance.path
|
||||
end
|
||||
tap_path = @tap ? @tap.path : CoreTap.instance.path
|
||||
@repository = T.let(tap_path, Pathname)
|
||||
end
|
||||
|
||||
sig { params(klass: Symbol, method: T.nilable(T.any(String, Symbol))).void }
|
||||
def test_header(klass, method: "run!")
|
||||
puts
|
||||
puts Formatter.headline("Running #{klass}##{method}", color: :magenta)
|
||||
end
|
||||
|
||||
sig { params(text: String).void }
|
||||
def info_header(text)
|
||||
puts Formatter.headline(text, color: :cyan)
|
||||
end
|
||||
|
||||
sig {
|
||||
params(
|
||||
arguments: String,
|
||||
named_args: T.nilable(T.any(String, T::Array[String])),
|
||||
env: T::Hash[String, String],
|
||||
verbose: T::Boolean,
|
||||
ignore_failures: T::Boolean,
|
||||
report_analytics: T::Boolean,
|
||||
).returns(Step)
|
||||
}
|
||||
def test(*arguments, named_args: nil, env: {}, verbose: @verbose, ignore_failures: false,
|
||||
report_analytics: false)
|
||||
step = Step.new(
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# typed: true # rubocop:todo Sorbet/StrictSigil
|
||||
# typed: strict
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "os"
|
||||
@@ -9,21 +9,23 @@ module Homebrew
|
||||
class TestCleanup < Test
|
||||
protected
|
||||
|
||||
ALLOWED_TAPS = [
|
||||
ALLOWED_TAPS = T.let([
|
||||
CoreTap.instance.name,
|
||||
CoreCaskTap.instance.name,
|
||||
].freeze
|
||||
].freeze, T::Array[String])
|
||||
|
||||
sig { params(repository: String).void }
|
||||
def reset_if_needed(repository)
|
||||
default_ref = default_origin_ref(repository)
|
||||
|
||||
return if system(git, "-C", repository, "diff", "--quiet", default_ref)
|
||||
return if system(git.to_s, "-C", repository, "diff", "--quiet", default_ref)
|
||||
|
||||
test git, "-C", repository, "reset", "--hard", default_ref
|
||||
test git.to_s, "-C", repository, "reset", "--hard", default_ref
|
||||
end
|
||||
|
||||
# Moving files is faster than removing them,
|
||||
# so move them if the current runner is ephemeral.
|
||||
sig { params(paths: T::Array[Pathname], sudo: T::Boolean).void }
|
||||
def delete_or_move(paths, sudo: false)
|
||||
return if paths.blank?
|
||||
|
||||
@@ -44,7 +46,7 @@ module Homebrew
|
||||
else
|
||||
paths.each do |path|
|
||||
if sudo
|
||||
test "sudo", "mv", path, Dir.mktmpdir
|
||||
test "sudo", "mv", path.to_s, Dir.mktmpdir
|
||||
else
|
||||
FileUtils.mv path, Dir.mktmpdir, force: true
|
||||
end
|
||||
@@ -52,13 +54,15 @@ module Homebrew
|
||||
end
|
||||
end
|
||||
|
||||
sig { void }
|
||||
def cleanup_shared
|
||||
FileUtils.chmod_R("u+X", HOMEBREW_CELLAR, force: true)
|
||||
|
||||
if repository.exist?
|
||||
cleanup_git_meta(repository)
|
||||
clean_if_needed(repository)
|
||||
prune_if_needed(repository)
|
||||
repo = repository.to_s
|
||||
cleanup_git_meta(repo)
|
||||
clean_if_needed(repo)
|
||||
prune_if_needed(repo)
|
||||
end
|
||||
|
||||
if HOMEBREW_REPOSITORY != HOMEBREW_PREFIX
|
||||
@@ -113,13 +117,14 @@ module Homebrew
|
||||
delete_or_move taps_to_remove
|
||||
|
||||
Pathname.glob("#{HOMEBREW_LIBRARY}/Taps/*/*").each do |git_repo|
|
||||
cleanup_git_meta(git_repo)
|
||||
git_repo_str = git_repo.to_s
|
||||
cleanup_git_meta(git_repo_str)
|
||||
next if repository == git_repo
|
||||
|
||||
checkout_branch_if_needed(git_repo)
|
||||
reset_if_needed(git_repo)
|
||||
clean_if_needed(git_repo)
|
||||
prune_if_needed(git_repo)
|
||||
checkout_branch_if_needed(git_repo_str)
|
||||
reset_if_needed(git_repo_str)
|
||||
clean_if_needed(git_repo_str)
|
||||
prune_if_needed(git_repo_str)
|
||||
end
|
||||
|
||||
# don't need to do `brew cleanup` unless we're self-hosted.
|
||||
@@ -130,6 +135,7 @@ module Homebrew
|
||||
|
||||
private
|
||||
|
||||
sig { params(repository: String).returns(String) }
|
||||
def default_origin_ref(repository)
|
||||
default_branch = Utils.popen_read(
|
||||
git, "-C", repository, "symbolic-ref", "refs/remotes/origin/HEAD", "--short"
|
||||
@@ -138,6 +144,7 @@ module Homebrew
|
||||
default_branch
|
||||
end
|
||||
|
||||
sig { params(repository: String).void }
|
||||
def checkout_branch_if_needed(repository)
|
||||
# We limit this to two parts, because branch names can have slashes in
|
||||
default_branch = default_origin_ref(repository).split("/", 2).last
|
||||
@@ -146,15 +153,17 @@ module Homebrew
|
||||
).strip
|
||||
return if default_branch == current_branch
|
||||
|
||||
test git, "-C", repository, "checkout", "-f", default_branch
|
||||
test git.to_s, "-C", repository, "checkout", "-f", default_branch.to_s
|
||||
end
|
||||
|
||||
sig { params(repository: String).void }
|
||||
def cleanup_git_meta(repository)
|
||||
pr_locks = "#{repository}/.git/refs/remotes/*/pr/*/*.lock"
|
||||
Dir.glob(pr_locks) { |lock| FileUtils.rm_f lock }
|
||||
FileUtils.rm_f "#{repository}/.git/gc.log"
|
||||
end
|
||||
|
||||
sig { params(repository: String).void }
|
||||
def clean_if_needed(repository)
|
||||
return if repository == HOMEBREW_PREFIX && HOMEBREW_PREFIX != HOMEBREW_REPOSITORY
|
||||
|
||||
@@ -168,15 +177,16 @@ module Homebrew
|
||||
git, "-C", repository, "clean", "--dry-run", *clean_args
|
||||
).strip.empty?
|
||||
|
||||
test git, "-C", repository, "clean", "-ff", *clean_args
|
||||
test git.to_s, "-C", repository, "clean", "-ff", *clean_args
|
||||
end
|
||||
|
||||
sig { params(repository: String).void }
|
||||
def prune_if_needed(repository)
|
||||
return unless Utils.safe_popen_read(
|
||||
"#{git} -C '#{repository}' -c gc.autoDetach=false gc --auto 2>&1",
|
||||
).include?("git prune")
|
||||
|
||||
test git, "-C", repository, "prune"
|
||||
test git.to_s, "-C", repository, "prune"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,36 +1,53 @@
|
||||
# typed: true # rubocop:todo Sorbet/StrictSigil
|
||||
# typed: strict
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Homebrew
|
||||
module TestBot
|
||||
class TestFormulae < Test
|
||||
sig { returns(T::Array[String]) }
|
||||
attr_accessor :skipped_or_failed_formulae
|
||||
|
||||
sig { returns(Pathname) }
|
||||
attr_reader :artifact_cache
|
||||
|
||||
sig {
|
||||
params(
|
||||
tap: T.nilable(Tap),
|
||||
git: T.nilable(String),
|
||||
dry_run: T::Boolean,
|
||||
fail_fast: T::Boolean,
|
||||
verbose: T::Boolean,
|
||||
).void
|
||||
}
|
||||
def initialize(tap:, git:, dry_run:, fail_fast:, verbose:)
|
||||
super
|
||||
|
||||
@skipped_or_failed_formulae = []
|
||||
@artifact_cache = Pathname.new("artifact-cache")
|
||||
@skipped_or_failed_formulae = T.let([], T::Array[String])
|
||||
@artifact_cache = T.let(Pathname.new("artifact-cache"), Pathname)
|
||||
# Let's keep track of the artifacts we've already downloaded
|
||||
# to avoid repeatedly trying to download the same thing.
|
||||
@downloaded_artifacts = Hash.new { |h, k| h[k] = [] }
|
||||
@downloaded_artifacts = T.let(Hash.new { |h, k| h[k] = [] }, T::Hash[String, T::Array[String]])
|
||||
@testing_formulae = T.let([], T::Array[String])
|
||||
@tested_formulae = T.let([], T::Array[String])
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
sig { returns(T.nilable(Pathname)) }
|
||||
def cached_event_json
|
||||
return unless (event_json = artifact_cache/"event.json").exist?
|
||||
|
||||
event_json
|
||||
end
|
||||
|
||||
sig { returns(T.nilable(T::Hash[String, T.untyped])) }
|
||||
def github_event_payload
|
||||
return if (github_event_path = ENV.fetch("GITHUB_EVENT_PATH", nil)).blank?
|
||||
|
||||
JSON.parse(File.read(github_event_path))
|
||||
end
|
||||
|
||||
sig { returns(T.nilable(String)) }
|
||||
def previous_github_sha
|
||||
return if tap.blank?
|
||||
return unless repository.directory?
|
||||
@@ -43,12 +60,22 @@ module Homebrew
|
||||
|
||||
# If we have a cached event payload, then we failed to get the artifact we wanted
|
||||
# from `GITHUB_EVENT_PATH`, so use the cached payload to check for a SHA1.
|
||||
event_payload = JSON.parse(cached_event_json.read) if cached_event_json.present?
|
||||
event_payload = JSON.parse(T.must(cached_event_json).read) if cached_event_json.present?
|
||||
event_payload ||= payload
|
||||
|
||||
event_payload.fetch("before", nil)
|
||||
end
|
||||
|
||||
sig {
|
||||
params(
|
||||
check_suite_nodes: T::Array[T::Hash[String, T.untyped]],
|
||||
repo: String,
|
||||
event_name: String,
|
||||
workflow_name: String,
|
||||
check_run_name: String,
|
||||
artifact_pattern: String,
|
||||
).returns(T::Array[T::Hash[String, T.untyped]])
|
||||
}
|
||||
def artifact_metadata(check_suite_nodes, repo, event_name, workflow_name, check_run_name, artifact_pattern)
|
||||
candidate_nodes = check_suite_nodes.select do |node|
|
||||
next false if node.fetch("status") != "COMPLETED"
|
||||
@@ -68,7 +95,7 @@ module Homebrew
|
||||
return [] if candidate_nodes.blank?
|
||||
|
||||
run_id = candidate_nodes.max_by { |node| Time.parse(node.fetch("updatedAt")) }
|
||||
.dig("workflowRun", "databaseId")
|
||||
&.dig("workflowRun", "databaseId")
|
||||
return [] if run_id.blank?
|
||||
|
||||
url = GitHub.url_to("repos", repo, "actions", "runs", run_id, "artifacts")
|
||||
@@ -111,12 +138,13 @@ module Homebrew
|
||||
}
|
||||
GRAPHQL
|
||||
|
||||
sig { params(artifact_pattern: String, dry_run: T::Boolean).void }
|
||||
def download_artifacts_from_previous_run!(artifact_pattern, dry_run:)
|
||||
return if dry_run
|
||||
return if GitHub::API.credentials_type == :none
|
||||
return if (sha = previous_github_sha).blank?
|
||||
|
||||
pull_number = github_event_payload.dig("pull_request", "number")
|
||||
pull_number = github_event_payload&.dig("pull_request", "number")
|
||||
return if pull_number.blank?
|
||||
|
||||
github_repository = ENV.fetch("GITHUB_REPOSITORY")
|
||||
@@ -147,7 +175,7 @@ module Homebrew
|
||||
return if wanted_artifacts.empty?
|
||||
|
||||
if (attempted_artifact = wanted_artifacts.find do |artifact|
|
||||
@downloaded_artifacts[sha].include?(artifact.fetch("name"))
|
||||
@downloaded_artifacts.fetch(sha).include?(artifact.fetch("name"))
|
||||
end)
|
||||
opoo "Already tried #{attempted_artifact.fetch("name")} from #{sha}, giving up"
|
||||
return
|
||||
@@ -163,7 +191,7 @@ module Homebrew
|
||||
wanted_artifacts.each do |artifact|
|
||||
name = artifact.fetch("name")
|
||||
ohai "Downloading artifact #{name} from #{sha}"
|
||||
@downloaded_artifacts[sha] << name
|
||||
@downloaded_artifacts.fetch(sha) << name
|
||||
|
||||
download_url = artifact.fetch("archive_download_url")
|
||||
artifact_id = artifact.fetch("id")
|
||||
@@ -180,30 +208,35 @@ module Homebrew
|
||||
opoo e
|
||||
end
|
||||
|
||||
sig { params(formula: Formula, git_ref: String).returns(T::Boolean) }
|
||||
def no_diff?(formula, git_ref)
|
||||
return false unless repository.directory?
|
||||
|
||||
@fetched_refs ||= []
|
||||
@fetched_refs ||= T.let([], T.nilable(T::Array[String]))
|
||||
if @fetched_refs.exclude?(git_ref)
|
||||
test git, "-C", repository, "fetch", "origin", git_ref, ignore_failures: true
|
||||
@fetched_refs << git_ref if steps.last.passed?
|
||||
test git.to_s, "-C", repository.to_s, "fetch", "origin", git_ref, ignore_failures: true
|
||||
@fetched_refs << git_ref if steps.fetch(-1).passed?
|
||||
end
|
||||
|
||||
relative_formula_path = formula.path.relative_path_from(repository)
|
||||
system(git, "-C", repository, "diff", "--no-ext-diff", "--quiet", git_ref, "--", relative_formula_path)
|
||||
!!system(git.to_s, "-C", repository.to_s, "diff", "--no-ext-diff", "--quiet", git_ref, "--",
|
||||
relative_formula_path.to_s)
|
||||
end
|
||||
|
||||
sig { params(formula: String, bottle_dir: Pathname).returns(T.nilable(T::Hash[String, T.untyped])) }
|
||||
def local_bottle_hash(formula, bottle_dir:)
|
||||
return if (local_bottle_json = bottle_glob(formula, bottle_dir, ".json").first).blank?
|
||||
|
||||
JSON.parse(local_bottle_json.read)
|
||||
end
|
||||
|
||||
sig { params(formula: Formula, formulae_dependents: T::Boolean).returns(T::Boolean) }
|
||||
def artifact_cache_valid?(formula, formulae_dependents: false)
|
||||
sha = if formulae_dependents
|
||||
previous_github_sha
|
||||
else
|
||||
local_bottle_hash(formula, bottle_dir: artifact_cache)&.dig(formula.name, "formula", "tap_git_revision")
|
||||
local_bottle_hash(formula.name, bottle_dir: artifact_cache)
|
||||
&.dig(formula.name, "formula", "tap_git_revision")
|
||||
end
|
||||
|
||||
return false if sha.blank?
|
||||
@@ -222,10 +255,26 @@ module Homebrew
|
||||
end
|
||||
end
|
||||
|
||||
sig {
|
||||
params(
|
||||
formula_name: String,
|
||||
bottle_dir: Pathname,
|
||||
ext: String,
|
||||
bottle_tag: String,
|
||||
).returns(T::Array[Pathname])
|
||||
}
|
||||
def bottle_glob(formula_name, bottle_dir = Pathname.pwd, ext = ".tar.gz", bottle_tag: Utils::Bottles.tag.to_s)
|
||||
bottle_dir.glob("#{formula_name}--*.#{bottle_tag}.bottle*#{ext}")
|
||||
end
|
||||
|
||||
sig {
|
||||
params(
|
||||
formula_name: String,
|
||||
testing_formulae_dependents: T::Boolean,
|
||||
dry_run: T::Boolean,
|
||||
bottle_dir: Pathname,
|
||||
).returns(T::Boolean)
|
||||
}
|
||||
def install_formula_from_bottle!(formula_name, testing_formulae_dependents:, dry_run:,
|
||||
bottle_dir: Pathname.pwd)
|
||||
bottle_filename = bottle_glob(formula_name, bottle_dir).first
|
||||
@@ -242,11 +291,11 @@ module Homebrew
|
||||
install_args = []
|
||||
install_args += %w[--ignore-dependencies --skip-post-install] if testing_formulae_dependents
|
||||
test "brew", "install", *install_args, bottle_filename
|
||||
install_step = steps.last
|
||||
install_step = steps.fetch(-1)
|
||||
|
||||
if !dry_run && !testing_formulae_dependents && install_step.passed?
|
||||
bottle_hash = local_bottle_hash(formula_name, bottle_dir:)
|
||||
bottle_revision = bottle_hash.dig(formula_name, "formula", "tap_git_revision")
|
||||
bottle_revision = bottle_hash&.dig(formula_name, "formula", "tap_git_revision")
|
||||
bottle_header = "Bottle cache hit"
|
||||
bottle_commit_details = if @fetched_refs&.include?(bottle_revision)
|
||||
Utils.safe_popen_read(git, "-C", repository, "show", "--format=reference", bottle_revision)
|
||||
@@ -259,7 +308,7 @@ module Homebrew
|
||||
puts GitHub::Actions::Annotation.new(
|
||||
:notice,
|
||||
bottle_message,
|
||||
file: bottle_hash.dig(formula_name, "formula", "tap_git_path"),
|
||||
file: bottle_hash&.dig(formula_name, "formula", "tap_git_path"),
|
||||
title: bottle_header,
|
||||
)
|
||||
else
|
||||
@@ -274,6 +323,7 @@ module Homebrew
|
||||
install_step.passed?
|
||||
end
|
||||
|
||||
sig { params(formula: Formula, no_older_versions: T::Boolean).returns(T::Boolean) }
|
||||
def bottled?(formula, no_older_versions: false)
|
||||
# If a formula has an `:all` bottle, then all its dependencies have
|
||||
# to be bottled too for us to use it. We only need to recurse
|
||||
@@ -289,10 +339,18 @@ module Homebrew
|
||||
end
|
||||
end
|
||||
|
||||
sig {
|
||||
params(
|
||||
formula: Formula,
|
||||
built_formulae: T::Enumerable[String],
|
||||
no_older_versions: T::Boolean,
|
||||
).returns(T::Boolean)
|
||||
}
|
||||
def bottled_or_built?(formula, built_formulae, no_older_versions: false)
|
||||
bottled?(formula, no_older_versions:) || built_formulae.include?(formula.full_name)
|
||||
end
|
||||
|
||||
sig { params(formula: Formula).returns(T::Boolean) }
|
||||
def downloads_using_homebrew_curl?(formula)
|
||||
[:stable, :head].any? do |spec_name|
|
||||
next false unless (spec = formula.send(spec_name))
|
||||
@@ -301,6 +359,7 @@ module Homebrew
|
||||
end
|
||||
end
|
||||
|
||||
sig { params(formula: Formula).void }
|
||||
def install_curl_if_needed(formula)
|
||||
return unless downloads_using_homebrew_curl?(formula)
|
||||
|
||||
@@ -308,6 +367,7 @@ module Homebrew
|
||||
env: { "HOMEBREW_DEVELOPER" => nil }
|
||||
end
|
||||
|
||||
sig { params(deps: T::Array[Dependency], reqs: T::Array[Requirement]).void }
|
||||
def install_mercurial_if_needed(deps, reqs)
|
||||
return if (deps | reqs).none? { |d| d.name == "mercurial" && d.build? }
|
||||
|
||||
@@ -315,6 +375,7 @@ module Homebrew
|
||||
env: { "HOMEBREW_DEVELOPER" => nil }
|
||||
end
|
||||
|
||||
sig { params(deps: T::Array[Dependency], reqs: T::Array[Requirement]).void }
|
||||
def install_subversion_if_needed(deps, reqs)
|
||||
return if (deps | reqs).none? { |d| d.name == "subversion" && d.build? }
|
||||
|
||||
@@ -322,6 +383,7 @@ module Homebrew
|
||||
env: { "HOMEBREW_DEVELOPER" => nil }
|
||||
end
|
||||
|
||||
sig { params(formula_name: String, reason: String).void }
|
||||
def skipped(formula_name, reason)
|
||||
@skipped_or_failed_formulae << formula_name
|
||||
|
||||
@@ -332,6 +394,7 @@ module Homebrew
|
||||
opoo reason
|
||||
end
|
||||
|
||||
sig { params(formula_name: String, reason: String).void }
|
||||
def failed(formula_name, reason)
|
||||
@skipped_or_failed_formulae << formula_name
|
||||
|
||||
@@ -342,6 +405,7 @@ module Homebrew
|
||||
onoe reason
|
||||
end
|
||||
|
||||
sig { params(formula: Formula).returns(T.nilable(String)) }
|
||||
def unsatisfied_requirements_messages(formula)
|
||||
f = Formulary.factory(formula.full_name)
|
||||
fi = FormulaInstaller.new(f, build_bottle: true)
|
||||
@@ -352,6 +416,7 @@ module Homebrew
|
||||
unsatisfied_requirements.values.flatten.map(&:message).join("\n").presence
|
||||
end
|
||||
|
||||
sig { params(keep_formulae: T::Array[String], args: Homebrew::Cmd::TestBotCmd::Args).void }
|
||||
def cleanup_during!(keep_formulae = [], args:)
|
||||
return unless cleanup?(args)
|
||||
return unless HOMEBREW_CACHE.exist?
|
||||
@@ -379,16 +444,20 @@ module Homebrew
|
||||
end
|
||||
|
||||
if @cleaned_up_during.blank?
|
||||
@cleaned_up_during = true
|
||||
@cleaned_up_during = T.let(true, T.nilable(T::Boolean))
|
||||
return
|
||||
end
|
||||
|
||||
installed_formulae = Utils.safe_popen_read("brew", "list", "--full-name", "--formulae").split("\n")
|
||||
uninstallable_formulae = installed_formulae - keep_formulae
|
||||
|
||||
@installed_formulae_deps ||= Hash.new do |h, formula|
|
||||
h[formula] = Utils.safe_popen_read("brew", "deps", "--full-name", formula).split("\n")
|
||||
end
|
||||
@installed_formulae_deps ||= T.let(
|
||||
Hash.new do |h, formula|
|
||||
h[formula] = Utils.safe_popen_read("brew", "deps", "--full-name", formula).split("\n")
|
||||
end,
|
||||
T.nilable(T::Hash[String, T::Array[String]]),
|
||||
)
|
||||
|
||||
uninstallable_formulae.reject! do |name|
|
||||
keep_formulae.any? { |f| @installed_formulae_deps[f].include?(name) }
|
||||
end
|
||||
@@ -398,6 +467,7 @@ module Homebrew
|
||||
test "brew", "uninstall", "--force", "--ignore-dependencies", *uninstallable_formulae
|
||||
end
|
||||
|
||||
sig { returns(T::Array[String]) }
|
||||
def sorted_formulae
|
||||
changed_formulae_dependents = {}
|
||||
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
# typed: strict
|
||||
|
||||
module Homebrew
|
||||
module TestBot
|
||||
class TestFormulae
|
||||
include Kernel
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,4 +1,4 @@
|
||||
# typed: true # rubocop:todo Sorbet/StrictSigil
|
||||
# typed: strict
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "test_bot/junit"
|
||||
@@ -17,238 +17,272 @@ require "test_bot/tap_syntax"
|
||||
module Homebrew
|
||||
module TestBot
|
||||
module TestRunner
|
||||
module_function
|
||||
|
||||
def ensure_blank_file_exists!(file)
|
||||
if file.exist?
|
||||
file.truncate(0)
|
||||
else
|
||||
FileUtils.touch(file)
|
||||
end
|
||||
end
|
||||
|
||||
def run!(tap, git:, args:)
|
||||
tests = T.let([], T::Array[Test])
|
||||
skip_setup = args.skip_setup?
|
||||
skip_cleanup_before = T.let(false, T::Boolean)
|
||||
|
||||
bottle_output_path = Pathname.new("bottle_output.txt")
|
||||
linkage_output_path = Pathname.new("linkage_output.txt")
|
||||
@skipped_or_failed_formulae_output_path = Pathname.new("skipped_or_failed_formulae-#{Utils::Bottles.tag}.txt")
|
||||
|
||||
if no_only_args?(args) || args.only_formulae?
|
||||
ensure_blank_file_exists!(bottle_output_path)
|
||||
ensure_blank_file_exists!(linkage_output_path)
|
||||
ensure_blank_file_exists!(@skipped_or_failed_formulae_output_path)
|
||||
end
|
||||
|
||||
output_paths = {
|
||||
bottle: bottle_output_path,
|
||||
linkage: linkage_output_path,
|
||||
skipped_or_failed_formulae: @skipped_or_failed_formulae_output_path,
|
||||
TestRunnerTypes = T.type_alias do
|
||||
{
|
||||
setup: T.nilable(Setup),
|
||||
tap_syntax: T.nilable(TapSyntax),
|
||||
formulae_detect: T.nilable(FormulaeDetect),
|
||||
formulae: T.nilable(Formulae),
|
||||
formulae_dependents: T.nilable(FormulaeDependents),
|
||||
cleanup_before: T.nilable(CleanupBefore),
|
||||
cleanup_after: T.nilable(CleanupAfter),
|
||||
bottles_fetch: T.nilable(BottlesFetch),
|
||||
}
|
||||
|
||||
test_bot_args = args.named.dup
|
||||
|
||||
# With no arguments just build the most recent commit.
|
||||
test_bot_args << "HEAD" if test_bot_args.empty?
|
||||
|
||||
test_bot_args.each do |argument|
|
||||
skip_cleanup_after = argument != test_bot_args.last
|
||||
current_tests = build_tests(argument, tap:,
|
||||
git:,
|
||||
output_paths:,
|
||||
skip_setup:,
|
||||
skip_cleanup_before:,
|
||||
skip_cleanup_after:,
|
||||
args:)
|
||||
skip_setup = true
|
||||
skip_cleanup_before = true
|
||||
tests += current_tests.values
|
||||
run_tests(current_tests, args:)
|
||||
end
|
||||
|
||||
failed_steps = tests.map(&:failed_steps)
|
||||
.flatten
|
||||
.compact
|
||||
ignored_steps = tests.map(&:ignored_steps)
|
||||
.flatten
|
||||
.compact
|
||||
steps_output = if failed_steps.blank? && ignored_steps.blank?
|
||||
"All steps passed!"
|
||||
else
|
||||
output_lines = []
|
||||
|
||||
if ignored_steps.present?
|
||||
output_lines += ["Warning: #{ignored_steps.count} failed step#{"s" if ignored_steps.count > 1} ignored!"]
|
||||
output_lines += ignored_steps.map(&:command_trimmed)
|
||||
end
|
||||
|
||||
if failed_steps.present?
|
||||
output_lines += ["Error: #{failed_steps.count} failed step#{"s" if failed_steps.count > 1}!"]
|
||||
output_lines += failed_steps.map(&:command_trimmed)
|
||||
end
|
||||
|
||||
output_lines.join("\n")
|
||||
end
|
||||
puts steps_output
|
||||
|
||||
steps_output_path = Pathname.new("steps_output.txt")
|
||||
steps_output_path.unlink if steps_output_path.exist?
|
||||
steps_output_path.write(steps_output)
|
||||
|
||||
if args.junit? && (no_only_args?(args) || args.only_formulae? || args.only_formulae_dependents?)
|
||||
junit_filters = %w[audit test]
|
||||
junit = Junit.new(tests)
|
||||
junit.build(filters: junit_filters)
|
||||
junit.write("brew-test-bot.xml")
|
||||
end
|
||||
|
||||
failed_steps.empty?
|
||||
end
|
||||
|
||||
def no_only_args?(args)
|
||||
any_only = args.only_cleanup_before? ||
|
||||
args.only_setup? ||
|
||||
args.only_tap_syntax? ||
|
||||
args.only_formulae? ||
|
||||
args.only_formulae_detect? ||
|
||||
args.only_formulae_dependents? ||
|
||||
args.only_bottles_fetch? ||
|
||||
args.only_cleanup_after?
|
||||
!any_only
|
||||
end
|
||||
class << self
|
||||
sig { params(tap: T.nilable(Tap), git: String, args: Homebrew::Cmd::TestBotCmd::Args).returns(T::Boolean) }
|
||||
def run!(tap, git:, args:)
|
||||
tests = T.let([], T::Array[Test])
|
||||
skip_setup = args.skip_setup?
|
||||
skip_cleanup_before = T.let(false, T::Boolean)
|
||||
|
||||
def build_tests(argument, tap:, git:, output_paths:, skip_setup:,
|
||||
skip_cleanup_before:, skip_cleanup_after:, args:)
|
||||
tests = {}
|
||||
bottle_output_path = Pathname.new("bottle_output.txt")
|
||||
linkage_output_path = Pathname.new("linkage_output.txt")
|
||||
skipped_or_failed_formulae_output_path = Pathname.new("skipped_or_failed_formulae-#{Utils::Bottles.tag}.txt")
|
||||
@skipped_or_failed_formulae_output_path = T.let(skipped_or_failed_formulae_output_path,
|
||||
T.nilable(Pathname))
|
||||
|
||||
no_only_args = no_only_args?(args)
|
||||
if no_only_args?(args) || args.only_formulae?
|
||||
ensure_blank_file_exists!(bottle_output_path)
|
||||
ensure_blank_file_exists!(linkage_output_path)
|
||||
ensure_blank_file_exists!(skipped_or_failed_formulae_output_path)
|
||||
end
|
||||
|
||||
if !skip_setup && (no_only_args || args.only_setup?)
|
||||
tests[:setup] = Setup.new(dry_run: args.dry_run?,
|
||||
fail_fast: args.fail_fast?,
|
||||
verbose: args.verbose?)
|
||||
output_paths = {
|
||||
bottle: bottle_output_path,
|
||||
linkage: linkage_output_path,
|
||||
skipped_or_failed_formulae: skipped_or_failed_formulae_output_path,
|
||||
}
|
||||
|
||||
test_bot_args = args.named.dup
|
||||
|
||||
# With no arguments just build the most recent commit.
|
||||
test_bot_args << "HEAD" if test_bot_args.empty?
|
||||
|
||||
test_bot_args.each do |argument|
|
||||
skip_cleanup_after = argument != test_bot_args.last
|
||||
current_tests = build_tests(argument, tap:,
|
||||
git:,
|
||||
output_paths:,
|
||||
skip_setup:,
|
||||
skip_cleanup_before:,
|
||||
skip_cleanup_after:,
|
||||
args:)
|
||||
skip_setup = true
|
||||
skip_cleanup_before = true
|
||||
tests += current_tests.values.compact
|
||||
run_tests(current_tests, args:)
|
||||
end
|
||||
|
||||
failed_steps = tests.map(&:failed_steps)
|
||||
.flatten
|
||||
.compact
|
||||
ignored_steps = tests.map(&:ignored_steps)
|
||||
.flatten
|
||||
.compact
|
||||
steps_output = if failed_steps.blank? && ignored_steps.blank?
|
||||
"All steps passed!"
|
||||
else
|
||||
output_lines = []
|
||||
|
||||
if ignored_steps.present?
|
||||
output_lines += [
|
||||
"Warning: #{ignored_steps.count} failed step#{"s" if ignored_steps.count > 1} ignored!",
|
||||
]
|
||||
output_lines += ignored_steps.map(&:command_trimmed)
|
||||
end
|
||||
|
||||
if failed_steps.present?
|
||||
output_lines += ["Error: #{failed_steps.count} failed step#{"s" if failed_steps.count > 1}!"]
|
||||
output_lines += failed_steps.map(&:command_trimmed)
|
||||
end
|
||||
|
||||
output_lines.join("\n")
|
||||
end
|
||||
puts steps_output
|
||||
|
||||
steps_output_path = Pathname.new("steps_output.txt")
|
||||
steps_output_path.unlink if steps_output_path.exist?
|
||||
steps_output_path.write(steps_output)
|
||||
|
||||
if args.junit? && (no_only_args?(args) || args.only_formulae? || args.only_formulae_dependents?)
|
||||
junit_filters = %w[audit test]
|
||||
junit = Junit.new(tests)
|
||||
junit.build(filters: junit_filters)
|
||||
junit.write("brew-test-bot.xml")
|
||||
end
|
||||
|
||||
failed_steps.empty?
|
||||
end
|
||||
|
||||
if no_only_args || args.only_tap_syntax?
|
||||
tests[:tap_syntax] = TapSyntax.new(tap: tap || CoreTap.instance,
|
||||
dry_run: args.dry_run?,
|
||||
private
|
||||
|
||||
sig { params(file: Pathname).void }
|
||||
def ensure_blank_file_exists!(file)
|
||||
if file.exist?
|
||||
file.truncate(0)
|
||||
else
|
||||
FileUtils.touch(file)
|
||||
end
|
||||
end
|
||||
|
||||
sig { params(args: Homebrew::Cmd::TestBotCmd::Args).returns(T::Boolean) }
|
||||
def no_only_args?(args)
|
||||
any_only = args.only_cleanup_before? ||
|
||||
args.only_setup? ||
|
||||
args.only_tap_syntax? ||
|
||||
args.only_formulae? ||
|
||||
args.only_formulae_detect? ||
|
||||
args.only_formulae_dependents? ||
|
||||
args.only_bottles_fetch? ||
|
||||
args.only_cleanup_after?
|
||||
!any_only
|
||||
end
|
||||
|
||||
sig {
|
||||
params(
|
||||
argument: String,
|
||||
tap: T.nilable(Tap),
|
||||
git: String,
|
||||
output_paths: T::Hash[Symbol, Pathname],
|
||||
skip_setup: T::Boolean,
|
||||
skip_cleanup_before: T::Boolean,
|
||||
skip_cleanup_after: T::Boolean,
|
||||
args: Homebrew::Cmd::TestBotCmd::Args,
|
||||
).returns(TestRunnerTypes)
|
||||
}
|
||||
def build_tests(argument, tap:, git:, output_paths:, skip_setup:,
|
||||
skip_cleanup_before:, skip_cleanup_after:, args:)
|
||||
no_only_args = no_only_args?(args)
|
||||
|
||||
if !skip_setup && (no_only_args || args.only_setup?)
|
||||
setup = Setup.new(dry_run: args.dry_run?,
|
||||
fail_fast: args.fail_fast?,
|
||||
verbose: args.verbose?)
|
||||
end
|
||||
|
||||
if no_only_args || args.only_tap_syntax?
|
||||
tap_syntax = TapSyntax.new(tap: tap || CoreTap.instance,
|
||||
dry_run: args.dry_run?,
|
||||
git:,
|
||||
fail_fast: args.fail_fast?,
|
||||
verbose: args.verbose?)
|
||||
end
|
||||
|
||||
no_formulae_flags = args.testing_formulae.nil? &&
|
||||
args.added_formulae.nil? &&
|
||||
args.deleted_formulae.nil?
|
||||
if no_formulae_flags && (no_only_args || args.only_formulae? || args.only_formulae_detect?)
|
||||
formulae_detect = FormulaeDetect.new(argument, tap:,
|
||||
git:,
|
||||
dry_run: args.dry_run?,
|
||||
fail_fast: args.fail_fast?,
|
||||
verbose: args.verbose?)
|
||||
end
|
||||
|
||||
if no_only_args || args.only_formulae?
|
||||
formulae = Formulae.new(tap:,
|
||||
git:,
|
||||
dry_run: args.dry_run?,
|
||||
fail_fast: args.fail_fast?,
|
||||
verbose: args.verbose?,
|
||||
output_paths:)
|
||||
end
|
||||
|
||||
if !args.skip_dependents? && (no_only_args || args.only_formulae? || args.only_formulae_dependents?)
|
||||
formulae_dependents = FormulaeDependents.new(tap:,
|
||||
git:,
|
||||
dry_run: args.dry_run?,
|
||||
fail_fast: args.fail_fast?,
|
||||
verbose: args.verbose?)
|
||||
end
|
||||
|
||||
if Homebrew::TestBot.cleanup?(args)
|
||||
if !skip_cleanup_before && (no_only_args || args.only_cleanup_before?)
|
||||
cleanup_before = CleanupBefore.new(tap:,
|
||||
git:,
|
||||
dry_run: args.dry_run?,
|
||||
fail_fast: args.fail_fast?,
|
||||
verbose: args.verbose?)
|
||||
end
|
||||
|
||||
if !skip_cleanup_after && (no_only_args || args.only_cleanup_after?)
|
||||
cleanup_after = CleanupAfter.new(tap:,
|
||||
git:,
|
||||
dry_run: args.dry_run?,
|
||||
fail_fast: args.fail_fast?,
|
||||
verbose: args.verbose?)
|
||||
end
|
||||
end
|
||||
|
||||
if args.only_bottles_fetch?
|
||||
bottles_fetch = BottlesFetch.new(tap:,
|
||||
git:,
|
||||
dry_run: args.dry_run?,
|
||||
fail_fast: args.fail_fast?,
|
||||
verbose: args.verbose?)
|
||||
end
|
||||
|
||||
no_formulae_flags = args.testing_formulae.nil? &&
|
||||
args.added_formulae.nil? &&
|
||||
args.deleted_formulae.nil?
|
||||
if no_formulae_flags && (no_only_args || args.only_formulae? || args.only_formulae_detect?)
|
||||
tests[:formulae_detect] = FormulaeDetect.new(argument, tap:,
|
||||
git:,
|
||||
dry_run: args.dry_run?,
|
||||
fail_fast: args.fail_fast?,
|
||||
verbose: args.verbose?)
|
||||
end
|
||||
|
||||
if no_only_args || args.only_formulae?
|
||||
tests[:formulae] = Formulae.new(tap:,
|
||||
git:,
|
||||
dry_run: args.dry_run?,
|
||||
fail_fast: args.fail_fast?,
|
||||
verbose: args.verbose?,
|
||||
output_paths:)
|
||||
end
|
||||
|
||||
if !args.skip_dependents? && (no_only_args || args.only_formulae? || args.only_formulae_dependents?)
|
||||
tests[:formulae_dependents] = FormulaeDependents.new(tap:,
|
||||
git:,
|
||||
dry_run: args.dry_run?,
|
||||
fail_fast: args.fail_fast?,
|
||||
verbose: args.verbose?)
|
||||
end
|
||||
|
||||
if Homebrew::TestBot.cleanup?(args)
|
||||
if !skip_cleanup_before && (no_only_args || args.only_cleanup_before?)
|
||||
tests[:cleanup_before] = CleanupBefore.new(tap:,
|
||||
git:,
|
||||
dry_run: args.dry_run?,
|
||||
fail_fast: args.fail_fast?,
|
||||
verbose: args.verbose?)
|
||||
end
|
||||
|
||||
if !skip_cleanup_after && (no_only_args || args.only_cleanup_after?)
|
||||
tests[:cleanup_after] = CleanupAfter.new(tap:,
|
||||
git:,
|
||||
dry_run: args.dry_run?,
|
||||
fail_fast: args.fail_fast?,
|
||||
verbose: args.verbose?)
|
||||
end
|
||||
{ setup:, tap_syntax:, formulae_detect:, formulae:, formulae_dependents:,
|
||||
cleanup_before:, cleanup_after:, bottles_fetch: }
|
||||
end
|
||||
|
||||
if args.only_bottles_fetch?
|
||||
tests[:bottles_fetch] = BottlesFetch.new(tap:,
|
||||
git:,
|
||||
dry_run: args.dry_run?,
|
||||
fail_fast: args.fail_fast?,
|
||||
verbose: args.verbose?)
|
||||
end
|
||||
sig { params(tests: TestRunnerTypes, args: Homebrew::Cmd::TestBotCmd::Args).void }
|
||||
def run_tests(tests, args:)
|
||||
tests[:cleanup_before]&.run!(args:)
|
||||
begin
|
||||
tests[:setup]&.run!(args:)
|
||||
tests[:tap_syntax]&.run!(args:)
|
||||
|
||||
tests
|
||||
end
|
||||
testing_formulae, added_formulae, deleted_formulae = if (detect_test = tests[:formulae_detect])
|
||||
detect_test.run!(args:)
|
||||
|
||||
def run_tests(tests, args:)
|
||||
tests[:cleanup_before]&.run!(args:)
|
||||
begin
|
||||
tests[:setup]&.run!(args:)
|
||||
tests[:tap_syntax]&.run!(args:)
|
||||
[
|
||||
detect_test.testing_formulae,
|
||||
detect_test.added_formulae,
|
||||
detect_test.deleted_formulae,
|
||||
]
|
||||
else
|
||||
[
|
||||
args.testing_formulae.to_a,
|
||||
args.added_formulae.to_a,
|
||||
args.deleted_formulae.to_a,
|
||||
]
|
||||
end
|
||||
|
||||
testing_formulae, added_formulae, deleted_formulae = if (detect_test = tests[:formulae_detect])
|
||||
detect_test.run!(args:)
|
||||
skipped_or_failed_formulae = if (formulae_test = tests[:formulae])
|
||||
formulae_test.testing_formulae = testing_formulae
|
||||
formulae_test.added_formulae = added_formulae
|
||||
formulae_test.deleted_formulae = deleted_formulae
|
||||
|
||||
[
|
||||
detect_test.testing_formulae,
|
||||
detect_test.added_formulae,
|
||||
detect_test.deleted_formulae,
|
||||
]
|
||||
else
|
||||
[
|
||||
args.testing_formulae.to_a,
|
||||
args.added_formulae.to_a,
|
||||
args.deleted_formulae.to_a,
|
||||
]
|
||||
formulae_test.run!(args:)
|
||||
|
||||
formulae_test.skipped_or_failed_formulae
|
||||
elsif args.skipped_or_failed_formulae.present?
|
||||
Array.new(T.must(args.skipped_or_failed_formulae))
|
||||
elsif T.must(@skipped_or_failed_formulae_output_path).exist?
|
||||
T.must(@skipped_or_failed_formulae_output_path).read.chomp.split(",")
|
||||
else
|
||||
[]
|
||||
end
|
||||
|
||||
if (dependents_test = tests[:formulae_dependents])
|
||||
dependents_test.testing_formulae = testing_formulae
|
||||
dependents_test.skipped_or_failed_formulae = skipped_or_failed_formulae
|
||||
dependents_test.tested_formulae = args.tested_formulae.to_a.presence || testing_formulae
|
||||
|
||||
dependents_test.run!(args:)
|
||||
end
|
||||
|
||||
if (fetch_test = tests[:bottles_fetch])
|
||||
fetch_test.testing_formulae = testing_formulae
|
||||
|
||||
fetch_test.run!(args:)
|
||||
end
|
||||
ensure
|
||||
tests[:cleanup_after]&.run!(args:)
|
||||
end
|
||||
|
||||
skipped_or_failed_formulae = if (formulae_test = tests[:formulae])
|
||||
formulae_test.testing_formulae = testing_formulae
|
||||
formulae_test.added_formulae = added_formulae
|
||||
formulae_test.deleted_formulae = deleted_formulae
|
||||
|
||||
formulae_test.run!(args:)
|
||||
|
||||
formulae_test.skipped_or_failed_formulae
|
||||
elsif args.skipped_or_failed_formulae.present?
|
||||
Array.new(args.skipped_or_failed_formulae)
|
||||
elsif @skipped_or_failed_formulae_output_path.exist?
|
||||
@skipped_or_failed_formulae_output_path.read.chomp.split(",")
|
||||
else
|
||||
[]
|
||||
end
|
||||
|
||||
if (dependents_test = tests[:formulae_dependents])
|
||||
dependents_test.testing_formulae = testing_formulae
|
||||
dependents_test.skipped_or_failed_formulae = skipped_or_failed_formulae
|
||||
dependents_test.tested_formulae = args.tested_formulae.to_a.presence || testing_formulae
|
||||
|
||||
dependents_test.run!(args:)
|
||||
end
|
||||
|
||||
if (fetch_test = tests[:bottles_fetch])
|
||||
fetch_test.testing_formulae = testing_formulae
|
||||
|
||||
fetch_test.run!(args:)
|
||||
end
|
||||
ensure
|
||||
tests[:cleanup_after]&.run!(args:)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
# typed: strict
|
||||
|
||||
module Homebrew
|
||||
module TestBot
|
||||
module TestRunner
|
||||
include Kernel
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user