diff --git a/Library/Homebrew/test_bot.rb b/Library/Homebrew/test_bot.rb index 239a5f0209c50..99ad9883299bd 100644 --- a/Library/Homebrew/test_bot.rb +++ b/Library/Homebrew/test_bot.rb @@ -1,4 +1,4 @@ -# typed: true # rubocop:todo Sorbet/StrictSigil +# typed: strict # frozen_string_literal: true require "test_bot/step" @@ -19,18 +19,21 @@ module Homebrew module TestBot module_function - GIT = "/usr/bin/git" + GIT = T.let("/usr/bin/git", String) - HOMEBREW_TAP_REGEX = %r{^([\w-]+)/homebrew-([\w-]+)$} + HOMEBREW_TAP_REGEX = T.let(%r{^([\w-]+)/homebrew-([\w-]+)$}, Regexp) + sig { params(args: Cmd::TestBotCmd::Args).returns(T::Boolean) } def cleanup?(args) args.cleanup? || GitHub::Actions.env_set? end + sig { params(args: 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 @@ def resolve_test_tap(tap = nil) end end + sig { params(args: Cmd::TestBotCmd::Args).void } def run!(args) $stdout.sync = true $stderr.sync = true diff --git a/Library/Homebrew/test_bot/bottles_fetch.rb b/Library/Homebrew/test_bot/bottles_fetch.rb index 7e50505dc0f9c..e71b1d725daf1 100644 --- a/Library/Homebrew/test_bot/bottles_fetch.rb +++ b/Library/Homebrew/test_bot/bottles_fetch.rb @@ -1,11 +1,13 @@ -# typed: true # rubocop:todo Sorbet/StrictSigil +# typed: strict # frozen_string_literal: true module Homebrew module TestBot class BottlesFetch < TestFormulae + sig { returns(T::Array[String]) } attr_accessor :testing_formulae + sig { params(args: Homebrew::CLI::Args).void } def run!(args:) info_header "Testing formulae:" puts testing_formulae @@ -19,6 +21,7 @@ def run!(args:) private + sig { params(formula_name: String, args: Homebrew::CLI::Args).void } def fetch_bottles!(formula_name, args:) test_header(:BottlesFetch, method: "fetch_bottles!(#{formula_name})") diff --git a/Library/Homebrew/test_bot/cleanup_after.rb b/Library/Homebrew/test_bot/cleanup_after.rb index 6935416e92d99..02d709a311348 100644 --- a/Library/Homebrew/test_bot/cleanup_after.rb +++ b/Library/Homebrew/test_bot/cleanup_after.rb @@ -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 @@ def run!(args:) private + sig { void } def pkill_if_needed pgrep = ["pgrep", "-f", HOMEBREW_CELLAR.to_s] diff --git a/Library/Homebrew/test_bot/cleanup_before.rb b/Library/Homebrew/test_bot/cleanup_before.rb index f5af2096163a7..96bf686ffa0c1 100644 --- a/Library/Homebrew/test_bot/cleanup_before.rb +++ b/Library/Homebrew/test_bot/cleanup_before.rb @@ -1,15 +1,14 @@ -# 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) - if tap.to_s != CoreTap.instance.name && CoreTap.instance.installed? - reset_if_needed(CoreTap.instance.path.to_s) - end + reset_if_needed(CoreTap.instance.path) if tap.to_s != CoreTap.instance.name && CoreTap.instance.installed? Pathname.glob("*.bottle*.*").each(&:unlink) diff --git a/Library/Homebrew/test_bot/formulae.rb b/Library/Homebrew/test_bot/formulae.rb index 98ec2c5fa7170..496fa7831cf92 100644 --- a/Library/Homebrew/test_bot/formulae.rb +++ b/Library/Homebrew/test_bot/formulae.rb @@ -1,21 +1,49 @@ -# 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]).returns(T::Array[String]) } + attr_writer :testing_formulae + + sig { params(added_formulae: T::Array[String]).returns(T::Array[String]) } + attr_writer :added_formulae + + sig { params(deleted_formulae: T::Array[String]).returns(T::Array[String]) } + 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(T.must(output_paths[:bottle]), Pathname) + @linkage_output_path = T.let(T.must(output_paths[:linkage]), Pathname) + @skipped_or_failed_formulae_output_path = T.let( + T.must(output_paths[: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]) + + @tested_formulae_count = T.let(0, Integer) + @testing_formulae_count = T.let(0, Integer) end + sig { params(args: Cmd::TestBotCmd::Args).void } def run!(args:) test_header(:Formulae) @@ -44,7 +72,6 @@ def run!(args:) # #run! modifies `@testing_formulae`, so we need to track this separately. @testing_formulae_count = @testing_formulae.count perform_bash_cleanup = @testing_formulae.include?("bash") - @tested_formulae_count = 0 sorted_formulae.each do |f| verify_local_bottles @@ -87,6 +114,7 @@ def run!(args:) 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 @@ def tap_needed_taps(deps) retry end + sig { void } def install_ca_certificates_if_needed return if DevelopmentTools.ca_file_handles_most_https_certificates? @@ -104,6 +133,7 @@ def install_ca_certificates_if_needed env: { "HOMEBREW_DEVELOPER" => nil } end + sig { params(formula: Formula, formula_name: String, args: 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) @@ -157,8 +187,8 @@ def setup_formulae_deps_instances(formula, formula_name, args:) end dependencies -= installed - @unchanged_dependencies = dependencies - @testing_formulae - unless @unchanged_dependencies.empty? + @unchanged_dependencies = T.let(dependencies - @testing_formulae, T.nilable(T::Array[Dependency])) + if @unchanged_dependencies.present? test "brew", "fetch", "--formulae", "--retry", *@unchanged_dependencies end @@ -188,17 +218,19 @@ def setup_formulae_deps_instances(formula, formula_name, args:) Utils.safe_popen_read("brew", "deps", "--formula", "--include-test", formula_name) .split("\n") build_dependencies = dependencies - runtime_or_test_dependencies - @unchanged_build_dependencies = build_dependencies - @testing_formulae + @unchanged_build_dependencies = T.let(build_dependencies - @testing_formulae, T.nilable(T::Array[Dependency])) 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,9 +277,10 @@ def verify_local_bottles end end + sig { params(formula: Formula, new_formula: T::Boolean, args: 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 @@ -282,16 +315,20 @@ def bottle_reinstall_formula(formula, new_formula, args:) return end - @bottle_filename = Pathname.new( - bottle_step.output - .gsub(%r{.*(\./\S+#{HOMEBREW_BOTTLES_EXTNAME_REGEX}).*}om, '\1'), + @bottle_filename = T.let( + Pathname.new( + bottle_step.output.gsub(%r{.*(\./\S+#{HOMEBREW_BOTTLES_EXTNAME_REGEX}).*}om, '\1'), + ), + T.nilable(Pathname), ) - @bottle_json_filename = Pathname.new( - @bottle_filename.to_s.gsub(/\.(\d+\.)?tar\.gz$/, ".json"), + + @bottle_json_filename = T.let( + Pathname.new(@bottle_filename.to_s.gsub(/\.(\d+\.)?tar\.gz$/, ".json")), + T.nilable(Pathname), ) - @bottle_checksums[@bottle_filename.realpath] = @bottle_filename.sha256 - @bottle_checksums[@bottle_json_filename.realpath] = @bottle_json_filename.sha256 + @bottle_checksums[T.must(@bottle_filename).realpath] = T.must(@bottle_filename).sha256 + @bottle_checksums[T.must(@bottle_json_filename).realpath] = T.must(@bottle_json_filename).sha256 @bottle_output_path.write(bottle_step.output, mode: "a") @@ -304,9 +341,9 @@ def bottle_reinstall_formula(formula, new_formula, args:) @testing_formulae.delete(formula.name) - unless @unchanged_build_dependencies.empty? + if @unchanged_build_dependencies.present? test "brew", "uninstall", "--formulae", "--force", "--ignore-dependencies", *@unchanged_build_dependencies - @unchanged_dependencies -= @unchanged_build_dependencies + @unchanged_dependencies -= @unchanged_build_dependencies if @unchanged_dependencies.present? end verify_attestations = if formula.name == "gh" @@ -320,6 +357,7 @@ def bottle_reinstall_formula(formula, new_formula, args:) env: { "HOMEBREW_VERIFY_ATTESTATIONS" => verify_attestations } end + sig { params(formula: Formula, args: 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 +372,7 @@ def build_bottle?(formula, args:) !args.build_from_source? end + sig { params(formula: Formula).void } def livecheck(formula) return unless formula.livecheck_defined? return if formula.livecheck.skip? @@ -395,6 +434,7 @@ def livecheck(formula) end end + sig { params(formula_name: String, args: Cmd::TestBotCmd::Args).void } def formula!(formula_name, args:) cleanup_during!(@testing_formulae, args:) @@ -595,7 +635,9 @@ def formula!(formula_name, args:) 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) + moved_artifacts = [@bottle_filename, @bottle_json_filename].map do |path| + T.must(path).realpath + end failed_dir.install moved_artifacts moved_artifacts.each do |old_location| @@ -613,13 +655,14 @@ def formula!(formula_name, args:) end ensure @tested_formulae_count += 1 - cleanup_bottle_etc_var(formula) if cleanup?(args) + cleanup_bottle_etc_var(T.must(formula)) if cleanup?(args) if @unchanged_dependencies.present? test "brew", "uninstall", "--formulae", "--force", "--ignore-dependencies", *@unchanged_dependencies end end + sig { params(formula_name: String).void } def portable_formula!(formula_name) test_header(:Formulae, method: "portable_formula!(#{formula_name})") @@ -655,6 +698,7 @@ def portable_formula!(formula_name) 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 +711,11 @@ def deleted_formula!(formula_name) formula_name end + sig { returns(T::Boolean) } def testing_portable_ruby? - tap&.core_tap? && @testing_formulae.include?("portable-ruby") + return false unless tap&.core_tap? + + @testing_formulae.include?("portable-ruby") end end end diff --git a/Library/Homebrew/test_bot/formulae_dependents.rb b/Library/Homebrew/test_bot/formulae_dependents.rb index a4cb436f81e4a..f5371abac7422 100644 --- a/Library/Homebrew/test_bot/formulae_dependents.rb +++ b/Library/Homebrew/test_bot/formulae_dependents.rb @@ -1,11 +1,16 @@ -# 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(args: Cmd::TestBotCmd::Args).void } def run!(args:) test "brew", "untap", "--force", "homebrew/cask" if !tap&.core_cask_tap? && CoreCaskTap.instance.installed? @@ -16,10 +21,9 @@ def run!(args:) info_header "Skipped or failed formulae:" puts skipped_or_failed_formulae - @testing_formulae_with_tested_dependents = [] - @tested_dependents_list = Pathname("tested-dependents-#{Utils::Bottles.tag}.txt") + @tested_dependents_list = Pathname.new("tested-dependents-#{Utils::Bottles.tag}.txt") - @dependent_testing_formulae = sorted_formulae - skipped_or_failed_formulae + @dependent_testing_formulae = T.let(sorted_formulae - skipped_or_failed_formulae, T.nilable(T::Array[String])) install_formulae_if_needed_from_bottles!(installable_bottles, args:) @@ -39,7 +43,7 @@ def run!(args:) [] end - @dependent_testing_formulae.each do |formula_name| + @dependent_testing_formulae&.each do |formula_name| dependent_formulae!(formula_name, args:) puts end @@ -49,13 +53,14 @@ def run!(args:) # Remove `bash` after it is tested, since leaving a broken `bash` # installation in the environment can cause issues with subsequent # GitHub Actions steps. - return unless @dependent_testing_formulae.include?("bash") + return unless @dependent_testing_formulae&.include?("bash") test "brew", "uninstall", "--formula", "--force", "bash" end private + sig { params(installable_bottles: T::Array[String], args: 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,8 +70,9 @@ def install_formulae_if_needed_from_bottles!(installable_bottles, args:) end end + sig { params(formula_name: String, args: Cmd::TestBotCmd::Args).void } def dependent_formulae!(formula_name, args:) - cleanup_during!(@dependent_testing_formulae, args:) + cleanup_during!(T.must(@dependent_testing_formulae), args:) test_header(:FormulaeDependents, method: "dependent_formulae!(#{formula_name})") @testing_formulae_with_tested_dependents << formula_name @@ -121,6 +127,7 @@ def dependent_formulae!(formula_name, args:) end end + sig { params(formula: Formula, formula_name: String, args: Cmd::TestBotCmd::Args).returns([[Formula], [Formula], [Formula]]) } def dependents_for_formula(formula, formula_name, args:) info_header "Determining dependents..." @@ -178,7 +185,7 @@ def dependents_for_formula(formula, formula_name, args:) # Defer formulae which could be tested later # i.e. formulae that also depend on something else yet to be built in this test run. dependents.reject! do |_, deps| - still_to_test = @dependent_testing_formulae - @testing_formulae_with_tested_dependents + still_to_test = T.must(@dependent_testing_formulae) - @testing_formulae_with_tested_dependents deps.map { |d| d.to_formula.full_name }.intersect?(still_to_test) end @@ -193,7 +200,7 @@ def dependents_for_formula(formula, formula_name, args:) # rubocop:enable Homebrew/MoveToExtendOS all_deps_bottled_or_built = deps.all? do |d| - bottled_or_built?(d.to_formula, @dependent_testing_formulae) + bottled_or_built?(d.to_formula, T.must(@dependent_testing_formulae)) end args.build_dependents_from_source? && all_deps_bottled_or_built end @@ -224,6 +231,14 @@ def dependents_for_formula(formula, formula_name, args:) [source_dependents, bottled_dependents, testable_dependents] end + sig { + params( + dependent: Formula, + testable_dependents: T::Array[Formula], + args: 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) && artifact_cache_valid?(dependent, formulae_dependents: true) @@ -234,7 +249,7 @@ def install_dependent(dependent, testable_dependents, args:, build_from_source: end if (messages = unsatisfied_requirements_messages(dependent)) - skipped dependent, messages + skipped dependent.name, messages return end @@ -244,7 +259,7 @@ def install_dependent(dependent, testable_dependents, args:, build_from_source: return end - cleanup_during!(@dependent_testing_formulae, args:) + cleanup_during!(T.must(@dependent_testing_formulae), args:) required_dependent_deps = dependent.deps.reject(&:optional?) bottled_on_current_version = bottled?(dependent, no_older_versions: true) @@ -388,6 +403,7 @@ def install_dependent(dependent, testable_dependents, args:, build_from_source: end end + sig { params(formula: Formula).void } def unlink_conflicts(formula) return if formula.keg_only? return if formula.linked_keg.exist? diff --git a/Library/Homebrew/test_bot/formulae_detect.rb b/Library/Homebrew/test_bot/formulae_detect.rb index 6998c4d843d92..5ff8051916e9d 100644 --- a/Library/Homebrew/test_bot/formulae_detect.rb +++ b/Library/Homebrew/test_bot/formulae_detect.rb @@ -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 = [] + @testing_formulae = T.let([], T::Array[String]) + @added_formulae = T.let([], T::Array[String]) + @deleted_formulae = T.let([], T::Array[String]) + @formulae_to_fetch = T.let([], T::Array[String]) end + sig { params(args: Cmd::TestBotCmd::Args).void } def run!(args:) detect_formulae!(args:) @@ -30,6 +43,7 @@ def run!(args:) private + sig { params(args: Cmd::TestBotCmd::Args).void } def detect_formulae!(args:) test_header(:FormulaeDetect, method: :detect_formulae!) @@ -132,11 +146,11 @@ def detect_formulae!(args:) if tap && diff_start_sha1 != diff_end_sha1 formula_path = tap.formula_dir.to_s @added_formulae += - diff_formulae(diff_start_sha1, diff_end_sha1, formula_path, "A") + diff_formulae(diff_start_sha1, diff_end_sha1, formula_path, "A") || [] modified_formulae += - diff_formulae(diff_start_sha1, diff_end_sha1, formula_path, "M") + diff_formulae(diff_start_sha1, diff_end_sha1, formula_path, "M") || [] @deleted_formulae += - diff_formulae(diff_start_sha1, diff_end_sha1, formula_path, "D") + diff_formulae(diff_start_sha1, diff_end_sha1, formula_path, "D") || [] end # If a formula is both added and deleted: it's actually modified. @@ -205,6 +219,7 @@ def detect_formulae!(args:) EOS end + sig { params(formula_name: String, args: 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 @@ -221,14 +236,26 @@ def safe_formula_canonical_name(formula_name, args:) 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.nilable(T::Array[String]), + ) + } def diff_formulae(start_revision, end_revision, path, filter) return unless tap diff --git a/Library/Homebrew/test_bot/junit.rb b/Library/Homebrew/test_bot/junit.rb index 1831530dd89d4..e6e4584796727 100644 --- a/Library/Homebrew/test_bot/junit.rb +++ b/Library/Homebrew/test_bot/junit.rb @@ -1,23 +1,26 @@ -# typed: true # rubocop:todo Sorbet/StrictSigil +# typed: strict # frozen_string_literal: true +require "rexml/document" + module Homebrew module TestBot # 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(REXML::Document.new, REXML::Document) end + sig { params(filters: T.nilable(T::Array[String])).void } def build(filters: nil) filters ||= [] - require "rexml/document" require "rexml/xmldecl" require "rexml/cdata" - @xml_document = REXML::Document.new @xml_document << REXML::XMLDecl.new testsuites = @xml_document.add_element "testsuites" @@ -45,6 +48,7 @@ def build(filters: nil) end end + sig { params(filename: String).void } def write(filename) output_path = Pathname(filename) output_path.unlink if output_path.exist? diff --git a/Library/Homebrew/test_bot/setup.rb b/Library/Homebrew/test_bot/setup.rb index 4e807595b66d1..faf671725e2fb 100644 --- a/Library/Homebrew/test_bot/setup.rb +++ b/Library/Homebrew/test_bot/setup.rb @@ -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::CLI::Args).returns(T.self_type) } def run!(args:) test_header(:Setup) diff --git a/Library/Homebrew/test_bot/step.rb b/Library/Homebrew/test_bot/step.rb index bd52e8f960caf..1eb330471dfec 100644 --- a/Library/Homebrew/test_bot/step.rb +++ b/Library/Homebrew/test_bot/step.rb @@ -1,4 +1,4 @@ -# typed: true # rubocop:todo Sorbet/StrictSigil +# typed: strict # frozen_string_literal: true require "system_command" @@ -11,24 +11,48 @@ module TestBot 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, :output + + sig { returns(T.nilable(Symbol)) } + attr_reader :status + + sig { returns(T.nilable(Time)) } + attr_reader :start_time, :end_time # Instantiates a Step object. # @param command [Array] Command to execute and arguments. # @param env [Hash] 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 - @env = env - @verbose = verbose - @ignore_failures = ignore_failures - @repository = repository - - @name = command[1]&.delete("-") - @status = :running - @output = nil + @named_args = T.let([named_args].flatten.compact.map(&:to_s), T::Array[String]) + @command = T.let(command + @named_args, T::Array[String]) + @env = T.let(env, T::Hash[String, String]) + @verbose = T.let(verbose, T::Boolean) + @ignore_failures = T.let(ignore_failures, T::Boolean) + @repository = T.let(repository, T.nilable(Pathname)) + + @name = T.let(command[1]&.delete("-"), T.nilable(String)) + @status = T.let(:running, T.nilable(Symbol)) + @output = T.let(nil, T.nilable(String)) + + @start_time = T.let(nil, T.nilable(Time)) + @end_time = T.let(nil, T.nilable(Time)) end + sig { returns(String) } def command_trimmed command.reject { |arg| arg.to_s.start_with?("--exclude") } .join(" ") @@ -37,6 +61,7 @@ def command_trimmed .delete_prefix("/usr/bin/") end + sig { returns(String) } def command_short (@command - %W[ brew @@ -56,26 +81,32 @@ def command_short .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,12 +122,14 @@ def puts_github_actions_annotation(message, title, file, line) 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 @@ -104,10 +137,12 @@ def output? # The execution time of the task. # Precondition: Step#run has been called. # @return [Float] execution time in seconds + sig { returns(Float) } def time - end_time - start_time + (T.must(end_time) - T.must(start_time)).to_f end + sig { void } def puts_full_output return if @output.blank? || @verbose @@ -116,9 +151,10 @@ def puts_full_output 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 = T.must(command.second).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 @@ def annotation_location(name) [formula.path, nil] end rescue FormulaUnavailableError - [@repository.glob("**/#{name}*").first, nil] + [T.must(@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,10 +189,11 @@ def truncate_output(output, max_kb:, context_lines:) 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 @@ -170,10 +208,13 @@ def run(dry_run: false, fail_fast: false) 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 diff --git a/Library/Homebrew/test_bot/tap_syntax.rb b/Library/Homebrew/test_bot/tap_syntax.rb index ee3a2d96d4e66..31c8efbc5b0f7 100644 --- a/Library/Homebrew/test_bot/tap_syntax.rb +++ b/Library/Homebrew/test_bot/tap_syntax.rb @@ -1,9 +1,10 @@ -# 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? diff --git a/Library/Homebrew/test_bot/test.rb b/Library/Homebrew/test_bot/test.rb index b70b8109282a7..eb564cdaa824b 100644 --- a/Library/Homebrew/test_bot/test.rb +++ b/Library/Homebrew/test_bot/test.rb @@ -17,6 +17,10 @@ def ignored_steps @steps.select(&:ignored?) end + def passed? + failed_steps.empty? + end + attr_reader :steps protected diff --git a/Library/Homebrew/test_bot/test_cleanup.rb b/Library/Homebrew/test_bot/test_cleanup.rb index e2819b6d1f9e3..1a88c50544119 100644 --- a/Library/Homebrew/test_bot/test_cleanup.rb +++ b/Library/Homebrew/test_bot/test_cleanup.rb @@ -1,4 +1,4 @@ -# typed: true # rubocop:todo Sorbet/StrictSigil +# typed: strict # frozen_string_literal: true require "os" @@ -9,21 +9,26 @@ module TestBot class TestCleanup < Test protected - ALLOWED_TAPS = [ - CoreTap.instance.name, - CoreCaskTap.instance.name, - ].freeze + ALLOWED_TAPS = T.let( + [ + CoreTap.instance.name, + CoreCaskTap.instance.name, + ].freeze, + T::Array[String], + ) + sig { params(repository: Pathname).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, "-C", repository.to_s, "diff", "--quiet", default_ref) - test git, "-C", repository, "reset", "--hard", default_ref + test git, "-C", repository.to_s, "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? @@ -52,6 +57,7 @@ def delete_or_move(paths, sudo: false) end end + sig { void } def cleanup_shared FileUtils.chmod_R("u+X", HOMEBREW_CELLAR, force: true) @@ -130,6 +136,7 @@ def cleanup_shared private + sig { params(repository: Pathname).returns(String) } def default_origin_ref(repository) default_branch = Utils.popen_read( git, "-C", repository, "symbolic-ref", "refs/remotes/origin/HEAD", "--short" @@ -138,6 +145,7 @@ def default_origin_ref(repository) default_branch end + sig { params(repository: Pathname).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 @@ -149,12 +157,14 @@ def checkout_branch_if_needed(repository) test git, "-C", repository, "checkout", "-f", default_branch end + sig { params(repository: Pathname).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: Pathname).void } def clean_if_needed(repository) return if repository == HOMEBREW_PREFIX && HOMEBREW_PREFIX != HOMEBREW_REPOSITORY @@ -171,6 +181,7 @@ def clean_if_needed(repository) test git, "-C", repository, "clean", "-ff", *clean_args end + sig { params(repository: Pathname).void } def prune_if_needed(repository) return unless Utils.safe_popen_read( "#{git} -C '#{repository}' -c gc.autoDetach=false gc --auto 2>&1", diff --git a/Library/Homebrew/test_bot/test_formulae.rb b/Library/Homebrew/test_bot/test_formulae.rb index 66ecce5b8683f..393c628f53dad 100644 --- a/Library/Homebrew/test_bot/test_formulae.rb +++ b/Library/Homebrew/test_bot/test_formulae.rb @@ -1,36 +1,58 @@ -# 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(T.any(CoreTap, Tap)), git: 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") + @skip_candidates = T.let([], T::Array[String]) + @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.let([], T::Array[String]) }, + T::Hash[String, T::Array[String]], + ) + + @testing_formulae = T.let([], T::Array[String]) + @tested_formulae = T.let([], T::Array[String]) + @testing_formulae_with_tested_dependents = T.let([], T::Array[String]) + @tested_dependents_list = T.let(Pathname.new("tested-dependents-placeholder.txt"), Pathname) 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 +65,20 @@ def previous_github_sha # 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 = if (cached_event = cached_event_json).present? + JSON.parse(cached_event.read) + end 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 +98,7 @@ def artifact_metadata(check_suite_nodes, repo, event_name, workflow_name, check_ 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 +141,13 @@ def artifact_metadata(check_suite_nodes, repo, event_name, workflow_name, check_ } 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 +178,7 @@ def download_artifacts_from_previous_run!(artifact_pattern, dry_run:) return if wanted_artifacts.empty? if (attempted_artifact = wanted_artifacts.find do |artifact| - @downloaded_artifacts[sha].include?(artifact.fetch("name")) + @downloaded_artifacts[sha]&.include?(artifact.fetch("name")) end) opoo "Already tried #{attempted_artifact.fetch("name")} from #{sha}, giving up" return @@ -163,7 +194,7 @@ def download_artifacts_from_previous_run!(artifact_pattern, dry_run:) wanted_artifacts.each do |artifact| name = artifact.fetch("name") ohai "Downloading artifact #{name} from #{sha}" - @downloaded_artifacts[sha] << name + T.must(@downloaded_artifacts[sha]) << name download_url = artifact.fetch("archive_download_url") artifact_id = artifact.fetch("id") @@ -180,30 +211,35 @@ def download_artifacts_from_previous_run!(artifact_pattern, dry_run:) 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? end relative_formula_path = formula.path.relative_path_from(repository) - system(git, "-C", repository, "diff", "--no-ext-diff", "--quiet", git_ref, "--", relative_formula_path) + T.must(system(git, "-C", repository, "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 +258,17 @@ def artifact_cache_valid?(formula, formulae_dependents: false) 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.nilable(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 @@ -246,9 +289,9 @@ def install_formula_from_bottle!(formula_name, testing_formulae_dependents:, dry 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 = T.must(bottle_hash).dig(formula_name, "formula", "tap_git_revision") bottle_header = "Bottle cache hit" - bottle_commit_details = if @fetched_refs&.include?(bottle_revision) + bottle_commit_details = if T.must(@fetched_refs).include?(bottle_revision) Utils.safe_popen_read(git, "-C", repository, "show", "--format=reference", bottle_revision) else bottle_revision @@ -259,7 +302,7 @@ def install_formula_from_bottle!(formula_name, testing_formulae_dependents:, dry puts GitHub::Actions::Annotation.new( :notice, bottle_message, - file: bottle_hash.dig(formula_name, "formula", "tap_git_path"), + file: T.must(bottle_hash).dig(formula_name, "formula", "tap_git_path"), title: bottle_header, ) else @@ -274,6 +317,7 @@ def install_formula_from_bottle!(formula_name, testing_formulae_dependents:, dry 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 +333,12 @@ def bottled?(formula, no_older_versions: false) end end + sig { params(formula: Formula, built_formulae: T::Array[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 +347,7 @@ def downloads_using_homebrew_curl?(formula) end end + sig { params(formula: Formula).void } def install_curl_if_needed(formula) return unless downloads_using_homebrew_curl?(formula) @@ -308,6 +355,7 @@ def install_curl_if_needed(formula) 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 +363,7 @@ def install_mercurial_if_needed(deps, reqs) 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 +371,7 @@ def install_subversion_if_needed(deps, reqs) 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 +382,7 @@ def skipped(formula_name, reason) opoo reason end + sig { params(formula_name: String, reason: String).void } def failed(formula_name, reason) @skipped_or_failed_formulae << formula_name @@ -342,6 +393,7 @@ def failed(formula_name, reason) 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 +404,7 @@ def unsatisfied_requirements_messages(formula) unsatisfied_requirements.values.flatten.map(&:message).join("\n").presence end + sig { params(keep_formulae: T::Array[String], args: Homebrew::CLI::Args).void } def cleanup_during!(keep_formulae = [], args:) return unless cleanup?(args) return unless HOMEBREW_CACHE.exist? @@ -378,16 +431,19 @@ def cleanup_during!(keep_formulae = [], args:) 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 @@ -397,6 +453,7 @@ def cleanup_during!(keep_formulae = [], args:) test "brew", "uninstall", "--force", "--ignore-dependencies", *uninstallable_formulae end + sig { returns(T::Array[String]) } def sorted_formulae changed_formulae_dependents = {} diff --git a/Library/Homebrew/test_bot/test_runner.rb b/Library/Homebrew/test_bot/test_runner.rb index 0e1309fc257da..60defed505bd2 100644 --- a/Library/Homebrew/test_bot/test_runner.rb +++ b/Library/Homebrew/test_bot/test_runner.rb @@ -1,4 +1,4 @@ -# typed: true # rubocop:todo Sorbet/StrictSigil +# typed: strict # frozen_string_literal: true require "test_bot/junit" @@ -19,6 +19,7 @@ module TestBot module TestRunner module_function + sig { params(file: Pathname).void } def ensure_blank_file_exists!(file) if file.exist? file.truncate(0) @@ -27,6 +28,7 @@ def ensure_blank_file_exists!(file) end end + sig { params(tap: T.nilable(Tap), git: String, args: Cmd::TestBotCmd::Args).returns(T::Boolean) } def run!(tap, git:, args:) tests = T.let([], T::Array[Test]) skip_setup = args.skip_setup? @@ -34,12 +36,15 @@ def run!(tap, git:, args:) 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( + Pathname.new("skipped_or_failed_formulae-#{Utils::Bottles.tag}.txt"), + T.nilable(Pathname), + ) 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) + ensure_blank_file_exists!(T.must(@skipped_or_failed_formulae_output_path)) end output_paths = { @@ -107,6 +112,7 @@ def run!(tap, git:, args:) failed_steps.empty? end + sig { params(args: Cmd::TestBotCmd::Args).returns(T::Boolean) } def no_only_args?(args) any_only = args.only_cleanup_before? || args.only_setup? || @@ -119,6 +125,18 @@ def no_only_args?(args) !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: Cmd::TestBotCmd::Args, + ).returns(T::Hash[Symbol, Test]) + } def build_tests(argument, tap:, git:, output_paths:, skip_setup:, skip_cleanup_before:, skip_cleanup_after:, args:) tests = {} @@ -196,6 +214,7 @@ def build_tests(argument, tap:, git:, output_paths:, skip_setup:, tests end + sig { params(tests: T::Hash[Symbol, T.untyped], args: Cmd::TestBotCmd::Args).void } def run_tests(tests, args:) tests[:cleanup_before]&.run!(args:) begin @@ -227,9 +246,9 @@ def run_tests(tests, 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(",") + [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