Merge pull request #15131 from Homebrew/test-runners
dev-cmd/determine-test-runners: add command to set test runners
This commit is contained in:
commit
4e20760a76
53
Library/Homebrew/dev-cmd/determine-test-runners.rb
Executable file
53
Library/Homebrew/dev-cmd/determine-test-runners.rb
Executable file
@ -0,0 +1,53 @@
|
||||
# typed: strict
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "cli/parser"
|
||||
require "test_runner_formula"
|
||||
require "github_runner_matrix"
|
||||
|
||||
module Homebrew
|
||||
extend T::Sig
|
||||
|
||||
sig { returns(Homebrew::CLI::Parser) }
|
||||
def self.determine_test_runners_args
|
||||
Homebrew::CLI::Parser.new do
|
||||
usage_banner <<~EOS
|
||||
`determine-test-runners` <testing-formulae> [<deleted-formulae>]
|
||||
|
||||
Determines the runners used to test formulae or their dependents.
|
||||
EOS
|
||||
switch "--eval-all",
|
||||
description: "Evaluate all available formulae, whether installed or not, to determine testing " \
|
||||
"dependents."
|
||||
switch "--dependents",
|
||||
description: "Determine runners for testing dependents. Requires `--eval-all` or `HOMEBREW_EVAL_ALL`."
|
||||
|
||||
named_args min: 1, max: 2
|
||||
|
||||
hide_from_man_page!
|
||||
end
|
||||
end
|
||||
|
||||
sig { void }
|
||||
def self.determine_test_runners
|
||||
args = determine_test_runners_args.parse
|
||||
|
||||
eval_all = args.eval_all? || Homebrew::EnvConfig.eval_all?
|
||||
|
||||
odie "`--dependents` requires `--eval-all` or `HOMEBREW_EVAL_ALL`!" if args.dependents? && !eval_all
|
||||
|
||||
testing_formulae = args.named.first.split(",")
|
||||
testing_formulae.map! { |name| TestRunnerFormula.new(Formulary.factory(name), eval_all: eval_all) }
|
||||
.freeze
|
||||
deleted_formulae = args.named.second&.split(",").freeze
|
||||
|
||||
runner_matrix = GitHubRunnerMatrix.new(testing_formulae, deleted_formulae, dependent_matrix: args.dependents?)
|
||||
runners = runner_matrix.active_runner_specs_hash
|
||||
|
||||
github_output = ENV.fetch("GITHUB_OUTPUT")
|
||||
File.open(github_output, "a") do |f|
|
||||
f.puts("runners=#{runners.to_json}")
|
||||
f.puts("runners_present=#{runners.present?}")
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -28,13 +28,13 @@ module OnSystem
|
||||
|
||||
return false if Homebrew::SimulateSystem.simulating_or_running_on_linux?
|
||||
|
||||
base_os = MacOS::Version.from_symbol(os_name)
|
||||
base_os = OS::Mac::Version.from_symbol(os_name)
|
||||
current_os = if Homebrew::SimulateSystem.current_os == :macos
|
||||
# Assume the oldest macOS version when simulating a generic macOS version
|
||||
# Version::NULL is always treated as less than any other version.
|
||||
Version::NULL
|
||||
else
|
||||
MacOS::Version.from_symbol(Homebrew::SimulateSystem.current_os)
|
||||
OS::Mac::Version.from_symbol(Homebrew::SimulateSystem.current_os)
|
||||
end
|
||||
|
||||
return current_os >= base_os if or_condition == :or_newer
|
||||
|
||||
35
Library/Homebrew/github_runner.rb
Normal file
35
Library/Homebrew/github_runner.rb
Normal file
@ -0,0 +1,35 @@
|
||||
# typed: strict
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "linux_runner_spec"
|
||||
require "macos_runner_spec"
|
||||
|
||||
class GitHubRunner < T::Struct
|
||||
extend T::Sig
|
||||
|
||||
const :platform, Symbol
|
||||
const :arch, Symbol
|
||||
const :spec, T.any(LinuxRunnerSpec, MacOSRunnerSpec)
|
||||
const :macos_version, T.nilable(OS::Mac::Version)
|
||||
prop :active, T::Boolean, default: false
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def macos?
|
||||
platform == :macos
|
||||
end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def linux?
|
||||
platform == :linux
|
||||
end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def x86_64?
|
||||
arch == :x86_64
|
||||
end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def arm64?
|
||||
arch == :arm64
|
||||
end
|
||||
end
|
||||
188
Library/Homebrew/github_runner_matrix.rb
Normal file
188
Library/Homebrew/github_runner_matrix.rb
Normal file
@ -0,0 +1,188 @@
|
||||
# typed: strict
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "test_runner_formula"
|
||||
require "github_runner"
|
||||
|
||||
class GitHubRunnerMatrix
|
||||
extend T::Sig
|
||||
|
||||
# FIXME: Enable cop again when https://github.com/sorbet/sorbet/issues/3532 is fixed.
|
||||
# rubocop:disable Style/MutableConstant
|
||||
MaybeStringArray = T.type_alias { T.nilable(T::Array[String]) }
|
||||
private_constant :MaybeStringArray
|
||||
|
||||
RunnerSpec = T.type_alias { T.any(LinuxRunnerSpec, MacOSRunnerSpec) }
|
||||
private_constant :RunnerSpec
|
||||
|
||||
MacOSRunnerSpecHash = T.type_alias { { name: String, runner: String, cleanup: T::Boolean } }
|
||||
private_constant :MacOSRunnerSpecHash
|
||||
|
||||
LinuxRunnerSpecHash = T.type_alias do
|
||||
{
|
||||
name: String,
|
||||
runner: String,
|
||||
container: T::Hash[Symbol, String],
|
||||
workdir: String,
|
||||
timeout: Integer,
|
||||
cleanup: T::Boolean,
|
||||
}
|
||||
end
|
||||
private_constant :LinuxRunnerSpecHash
|
||||
|
||||
RunnerSpecHash = T.type_alias { T.any(LinuxRunnerSpecHash, MacOSRunnerSpecHash) }
|
||||
private_constant :RunnerSpecHash
|
||||
# rubocop:enable Style/MutableConstant
|
||||
|
||||
sig { returns(T::Array[GitHubRunner]) }
|
||||
attr_reader :runners
|
||||
|
||||
sig {
|
||||
params(
|
||||
testing_formulae: T::Array[TestRunnerFormula],
|
||||
deleted_formulae: MaybeStringArray,
|
||||
dependent_matrix: T::Boolean,
|
||||
).void
|
||||
}
|
||||
def initialize(testing_formulae, deleted_formulae, dependent_matrix:)
|
||||
@testing_formulae = T.let(testing_formulae, T::Array[TestRunnerFormula])
|
||||
@deleted_formulae = T.let(deleted_formulae, MaybeStringArray)
|
||||
@dependent_matrix = T.let(dependent_matrix, T::Boolean)
|
||||
|
||||
@runners = T.let([], T::Array[GitHubRunner])
|
||||
generate_runners!
|
||||
|
||||
freeze
|
||||
end
|
||||
|
||||
sig { returns(T::Array[RunnerSpecHash]) }
|
||||
def active_runner_specs_hash
|
||||
runners.select(&:active)
|
||||
.map(&:spec)
|
||||
.map(&:to_h)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
sig { returns(LinuxRunnerSpec) }
|
||||
def linux_runner_spec
|
||||
linux_runner = ENV.fetch("HOMEBREW_LINUX_RUNNER")
|
||||
linux_cleanup = ENV.fetch("HOMEBREW_LINUX_CLEANUP")
|
||||
|
||||
LinuxRunnerSpec.new(
|
||||
name: "Linux",
|
||||
runner: linux_runner,
|
||||
container: {
|
||||
image: "ghcr.io/homebrew/ubuntu22.04:master",
|
||||
options: "--user=linuxbrew -e GITHUB_ACTIONS_HOMEBREW_SELF_HOSTED",
|
||||
},
|
||||
workdir: "/github/home",
|
||||
timeout: 4320,
|
||||
cleanup: linux_cleanup == "true",
|
||||
)
|
||||
end
|
||||
|
||||
VALID_PLATFORMS = T.let([:macos, :linux].freeze, T::Array[Symbol])
|
||||
VALID_ARCHES = T.let([:arm64, :x86_64].freeze, T::Array[Symbol])
|
||||
|
||||
sig {
|
||||
params(
|
||||
platform: Symbol,
|
||||
arch: Symbol,
|
||||
spec: RunnerSpec,
|
||||
macos_version: T.nilable(OS::Mac::Version),
|
||||
).returns(GitHubRunner)
|
||||
}
|
||||
def create_runner(platform, arch, spec, macos_version = nil)
|
||||
raise "Unexpected platform: #{platform}" if VALID_PLATFORMS.exclude?(platform)
|
||||
raise "Unexpected arch: #{arch}" if VALID_ARCHES.exclude?(arch)
|
||||
|
||||
runner = GitHubRunner.new(platform: platform, arch: arch, spec: spec, macos_version: macos_version)
|
||||
runner.active = active_runner?(runner)
|
||||
runner.freeze
|
||||
end
|
||||
|
||||
sig { void }
|
||||
def generate_runners!
|
||||
return if @runners.present?
|
||||
|
||||
@runners << create_runner(:linux, :x86_64, linux_runner_spec)
|
||||
|
||||
github_run_id = ENV.fetch("GITHUB_RUN_ID")
|
||||
github_run_attempt = ENV.fetch("GITHUB_RUN_ATTEMPT")
|
||||
ephemeral_suffix = "-#{github_run_id}-#{github_run_attempt}"
|
||||
|
||||
MacOSVersions::SYMBOLS.each_value do |version|
|
||||
macos_version = OS::Mac::Version.new(version)
|
||||
next if macos_version.unsupported_release?
|
||||
|
||||
spec = MacOSRunnerSpec.new(
|
||||
name: "macOS #{version}-x86_64",
|
||||
runner: "#{version}#{ephemeral_suffix}",
|
||||
cleanup: false,
|
||||
)
|
||||
@runners << create_runner(:macos, :x86_64, spec, macos_version)
|
||||
|
||||
next unless macos_version >= :big_sur
|
||||
|
||||
# Use bare metal runner when testing dependents on ARM64 Monterey.
|
||||
runner, cleanup = if (macos_version >= :ventura && @dependent_matrix) || macos_version >= :monterey
|
||||
["#{version}-arm64#{ephemeral_suffix}", false]
|
||||
else
|
||||
["#{version}-arm64", true]
|
||||
end
|
||||
|
||||
spec = MacOSRunnerSpec.new(name: "macOS #{version}-arm64", runner: runner, cleanup: cleanup)
|
||||
@runners << create_runner(:macos, :arm64, spec, macos_version)
|
||||
end
|
||||
|
||||
@runners.freeze
|
||||
end
|
||||
|
||||
sig { params(runner: GitHubRunner).returns(T::Boolean) }
|
||||
def active_runner?(runner)
|
||||
if @dependent_matrix
|
||||
formulae_have_untested_dependents?(runner)
|
||||
else
|
||||
return true if @deleted_formulae.present?
|
||||
|
||||
compatible_formulae = @testing_formulae.dup
|
||||
|
||||
platform = runner.platform
|
||||
arch = runner.arch
|
||||
macos_version = runner.macos_version
|
||||
|
||||
compatible_formulae.select! { |formula| formula.public_send(:"#{platform}_compatible?") }
|
||||
compatible_formulae.select! { |formula| formula.public_send(:"#{arch}_compatible?") }
|
||||
compatible_formulae.select! { |formula| formula.compatible_with?(macos_version) } if macos_version
|
||||
|
||||
compatible_formulae.present?
|
||||
end
|
||||
end
|
||||
|
||||
sig { params(runner: GitHubRunner).returns(T::Boolean) }
|
||||
def formulae_have_untested_dependents?(runner)
|
||||
platform = runner.platform
|
||||
arch = runner.arch
|
||||
macos_version = runner.macos_version
|
||||
|
||||
@testing_formulae.any? do |formula|
|
||||
# If the formula has a platform/arch/macOS version requirement, then its
|
||||
# dependents don't need to be tested if these requirements are not satisfied.
|
||||
next false unless formula.public_send(:"#{platform}_compatible?")
|
||||
next false unless formula.public_send(:"#{arch}_compatible?")
|
||||
next false if macos_version.present? && !formula.compatible_with?(macos_version)
|
||||
|
||||
compatible_dependents = formula.dependents(platform: platform, arch: arch, macos_version: macos_version&.to_sym)
|
||||
.dup
|
||||
|
||||
compatible_dependents.select! { |dependent_f| dependent_f.public_send(:"#{platform}_compatible?") }
|
||||
compatible_dependents.select! { |dependent_f| dependent_f.public_send(:"#{arch}_compatible?") }
|
||||
compatible_dependents.select! { |dependent_f| dependent_f.compatible_with?(macos_version) } if macos_version
|
||||
|
||||
# These arrays will generally have been generated by different Formulary caches,
|
||||
# so we can only compare them by name and not directly.
|
||||
(compatible_dependents.map(&:name) - @testing_formulae.map(&:name)).present?
|
||||
end
|
||||
end
|
||||
end
|
||||
34
Library/Homebrew/linux_runner_spec.rb
Normal file
34
Library/Homebrew/linux_runner_spec.rb
Normal file
@ -0,0 +1,34 @@
|
||||
# typed: strict
|
||||
# frozen_string_literal: true
|
||||
|
||||
class LinuxRunnerSpec < T::Struct
|
||||
extend T::Sig
|
||||
|
||||
const :name, String
|
||||
const :runner, String
|
||||
const :container, T::Hash[Symbol, String]
|
||||
const :workdir, String
|
||||
const :timeout, Integer
|
||||
const :cleanup, T::Boolean
|
||||
|
||||
sig {
|
||||
returns({
|
||||
name: String,
|
||||
runner: String,
|
||||
container: T::Hash[Symbol, String],
|
||||
workdir: String,
|
||||
timeout: Integer,
|
||||
cleanup: T::Boolean,
|
||||
})
|
||||
}
|
||||
def to_h
|
||||
{
|
||||
name: name,
|
||||
runner: runner,
|
||||
container: container,
|
||||
workdir: workdir,
|
||||
timeout: timeout,
|
||||
cleanup: cleanup,
|
||||
}
|
||||
end
|
||||
end
|
||||
19
Library/Homebrew/macos_runner_spec.rb
Normal file
19
Library/Homebrew/macos_runner_spec.rb
Normal file
@ -0,0 +1,19 @@
|
||||
# typed: strict
|
||||
# frozen_string_literal: true
|
||||
|
||||
class MacOSRunnerSpec < T::Struct
|
||||
extend T::Sig
|
||||
|
||||
const :name, String
|
||||
const :runner, String
|
||||
const :cleanup, T::Boolean
|
||||
|
||||
sig { returns({ name: String, runner: String, cleanup: T::Boolean }) }
|
||||
def to_h
|
||||
{
|
||||
name: name,
|
||||
runner: runner,
|
||||
cleanup: cleanup,
|
||||
}
|
||||
end
|
||||
end
|
||||
@ -72,6 +72,11 @@ module OS
|
||||
self >= HOMEBREW_MACOS_NEWEST_UNSUPPORTED
|
||||
end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def unsupported_release?
|
||||
outdated_release? || prerelease?
|
||||
end
|
||||
|
||||
# For {OS::Mac::Version} compatibility.
|
||||
sig { returns(T::Boolean) }
|
||||
def requires_nehalem_cpu?
|
||||
|
||||
@ -23,9 +23,9 @@ class MacOSRequirement < Requirement
|
||||
def initialize(tags = [], comparator: ">=")
|
||||
@version = begin
|
||||
if comparator == "==" && tags.first.respond_to?(:map)
|
||||
tags.first.map { |s| MacOS::Version.from_symbol(s) }
|
||||
tags.first.map { |s| OS::Mac::Version.from_symbol(s) }
|
||||
else
|
||||
MacOS::Version.from_symbol(tags.first) unless tags.empty?
|
||||
OS::Mac::Version.from_symbol(tags.first) unless tags.empty?
|
||||
end
|
||||
rescue MacOSVersionError => e
|
||||
if DISABLED_MACOS_VERSIONS.include?(e.version)
|
||||
@ -43,7 +43,7 @@ class MacOSRequirement < Requirement
|
||||
end
|
||||
|
||||
# Otherwise fallback to the oldest allowed if comparator is >=.
|
||||
MacOS::Version.new(HOMEBREW_MACOS_OLDEST_ALLOWED) if comparator == ">="
|
||||
OS::Mac::Version.new(HOMEBREW_MACOS_OLDEST_ALLOWED) if comparator == ">="
|
||||
end
|
||||
|
||||
@comparator = comparator
|
||||
@ -56,7 +56,7 @@ class MacOSRequirement < Requirement
|
||||
|
||||
satisfy(build_env: false) do
|
||||
T.bind(self, MacOSRequirement)
|
||||
next Array(@version).any? { |v| MacOS.version.compare(@comparator, v) } if OS.mac? && version_specified?
|
||||
next Array(@version).any? { |v| OS::Mac.version.compare(@comparator, v) } if OS.mac? && version_specified?
|
||||
next true if OS.mac?
|
||||
next true if @version
|
||||
|
||||
|
||||
75
Library/Homebrew/test/dev-cmd/determine-test-runners_spec.rb
Normal file
75
Library/Homebrew/test/dev-cmd/determine-test-runners_spec.rb
Normal file
@ -0,0 +1,75 @@
|
||||
# typed: false
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "dev-cmd/determine-test-runners"
|
||||
require "cmd/shared_examples/args_parse"
|
||||
|
||||
describe "brew determine-test-runners" do
|
||||
after do
|
||||
FileUtils.rm_f github_output
|
||||
end
|
||||
|
||||
let(:linux_runner) { "ubuntu-22.04" }
|
||||
# We need to make sure we write to a different path for each example.
|
||||
let(:github_output) { "#{TEST_TMPDIR}/github_output#{DetermineRunnerTestHelper.new.number}" }
|
||||
let(:ephemeral_suffix) { "-12345-1" }
|
||||
let(:runner_env) do
|
||||
{
|
||||
"HOMEBREW_LINUX_RUNNER" => linux_runner,
|
||||
"HOMEBREW_LINUX_CLEANUP" => "false",
|
||||
"GITHUB_RUN_ID" => ephemeral_suffix.split("-").second,
|
||||
"GITHUB_RUN_ATTEMPT" => ephemeral_suffix.split("-").third,
|
||||
}.freeze
|
||||
end
|
||||
let(:all_runners) do
|
||||
out = []
|
||||
MacOSVersions::SYMBOLS.each_value do |v|
|
||||
macos_version = OS::Mac::Version.new(v)
|
||||
next if macos_version.unsupported_release?
|
||||
|
||||
out << v
|
||||
out << "#{v}-arm64"
|
||||
end
|
||||
|
||||
out << linux_runner
|
||||
|
||||
out
|
||||
end
|
||||
|
||||
it_behaves_like "parseable arguments"
|
||||
|
||||
it "assigns all runners for formulae without any requirements", :integration_test do
|
||||
setup_test_formula "testball"
|
||||
|
||||
expect { brew "determine-test-runners", "testball", runner_env.merge({ "GITHUB_OUTPUT" => github_output }) }
|
||||
.to not_to_output.to_stdout
|
||||
.and not_to_output.to_stderr
|
||||
.and be_a_success
|
||||
|
||||
expect(File.read(github_output)).not_to be_empty
|
||||
expect(get_runners(github_output).sort).to eq(all_runners.sort)
|
||||
end
|
||||
end
|
||||
|
||||
def get_runners(file)
|
||||
runner_line = File.open(file).first
|
||||
json_text = runner_line[/runners=(.*)/, 1]
|
||||
runner_hash = JSON.parse(json_text)
|
||||
runner_hash.map { |item| item["runner"].delete_suffix(ephemeral_suffix) }
|
||||
.sort
|
||||
end
|
||||
|
||||
class DetermineRunnerTestHelper
|
||||
@instances = 0
|
||||
|
||||
class << self
|
||||
attr_accessor :instances
|
||||
end
|
||||
|
||||
attr_reader :number
|
||||
|
||||
def initialize
|
||||
self.class.instances += 1
|
||||
@number = self.class.instances
|
||||
end
|
||||
end
|
||||
288
Library/Homebrew/test/github_runner_matrix_spec.rb
Normal file
288
Library/Homebrew/test/github_runner_matrix_spec.rb
Normal file
@ -0,0 +1,288 @@
|
||||
# typed: false
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "github_runner_matrix"
|
||||
require "test/support/fixtures/testball"
|
||||
|
||||
describe GitHubRunnerMatrix do
|
||||
before do
|
||||
allow(ENV).to receive(:fetch).with("HOMEBREW_LINUX_RUNNER").and_return("ubuntu-latest")
|
||||
allow(ENV).to receive(:fetch).with("HOMEBREW_LINUX_CLEANUP").and_return("false")
|
||||
allow(ENV).to receive(:fetch).with("GITHUB_RUN_ID").and_return("12345")
|
||||
allow(ENV).to receive(:fetch).with("GITHUB_RUN_ATTEMPT").and_return("1")
|
||||
end
|
||||
|
||||
let(:newest_supported_macos) do
|
||||
MacOSVersions::SYMBOLS.find { |_, v| !OS::Mac::Version.new(v).prerelease? }
|
||||
end
|
||||
|
||||
let(:testball) { TestRunnerFormula.new(Testball.new) }
|
||||
let(:testball_depender) { setup_test_runner_formula("testball-depender", ["testball"]) }
|
||||
let(:testball_depender_linux) { setup_test_runner_formula("testball-depender-linux", ["testball", :linux]) }
|
||||
let(:testball_depender_macos) { setup_test_runner_formula("testball-depender-macos", ["testball", :macos]) }
|
||||
let(:testball_depender_intel) do
|
||||
setup_test_runner_formula("testball-depender-intel", ["testball", { arch: :x86_64 }])
|
||||
end
|
||||
let(:testball_depender_arm) { setup_test_runner_formula("testball-depender-arm", ["testball", { arch: :arm64 }]) }
|
||||
let(:testball_depender_newest) do
|
||||
symbol, = newest_supported_macos
|
||||
setup_test_runner_formula("testball-depender-newest", ["testball", { macos: symbol }])
|
||||
end
|
||||
|
||||
describe "#active_runner_specs_hash" do
|
||||
it "returns an object that responds to `#to_json`" do
|
||||
expect(
|
||||
described_class.new([], ["deleted"], dependent_matrix: false)
|
||||
.active_runner_specs_hash
|
||||
.respond_to?(:to_json),
|
||||
).to be(true)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#generate_runners!" do
|
||||
it "is idempotent" do
|
||||
matrix = described_class.new([], [], dependent_matrix: false)
|
||||
runners = matrix.runners.dup
|
||||
matrix.send(:generate_runners!)
|
||||
|
||||
expect(matrix.runners).to eq(runners)
|
||||
end
|
||||
end
|
||||
|
||||
context "when there are no testing formulae and no deleted formulae" do
|
||||
it "activates no test runners" do
|
||||
expect(described_class.new([], [], dependent_matrix: false).runners.any?(&:active))
|
||||
.to be(false)
|
||||
end
|
||||
|
||||
it "activates no dependent runners" do
|
||||
expect(described_class.new([], [], dependent_matrix: true).runners.any?(&:active))
|
||||
.to be(false)
|
||||
end
|
||||
end
|
||||
|
||||
context "when there are testing formulae and no deleted formulae" do
|
||||
context "when it is a matrix for the `tests` job" do
|
||||
context "when testing formulae have no requirements" do
|
||||
it "activates all runners" do
|
||||
expect(described_class.new([testball], [], dependent_matrix: false).runners.all?(&:active))
|
||||
.to be(true)
|
||||
end
|
||||
end
|
||||
|
||||
context "when testing formulae require Linux" do
|
||||
it "activates only the Linux runner" do
|
||||
runner_matrix = described_class.new([testball_depender_linux], [], dependent_matrix: false)
|
||||
|
||||
expect(runner_matrix.runners.all?(&:active)).to be(false)
|
||||
expect(runner_matrix.runners.any?(&:active)).to be(true)
|
||||
expect(get_runner_names(runner_matrix)).to eq(["Linux"])
|
||||
end
|
||||
end
|
||||
|
||||
context "when testing formulae require macOS" do
|
||||
it "activates only the macOS runners" do
|
||||
runner_matrix = described_class.new([testball_depender_macos], [], dependent_matrix: false)
|
||||
|
||||
expect(runner_matrix.runners.all?(&:active)).to be(false)
|
||||
expect(runner_matrix.runners.any?(&:active)).to be(true)
|
||||
expect(get_runner_names(runner_matrix)).to eq(get_runner_names(runner_matrix, :macos?))
|
||||
end
|
||||
end
|
||||
|
||||
context "when testing formulae require Intel" do
|
||||
it "activates only the Intel runners" do
|
||||
runner_matrix = described_class.new([testball_depender_intel], [], dependent_matrix: false)
|
||||
|
||||
expect(runner_matrix.runners.all?(&:active)).to be(false)
|
||||
expect(runner_matrix.runners.any?(&:active)).to be(true)
|
||||
expect(get_runner_names(runner_matrix)).to eq(get_runner_names(runner_matrix, :x86_64?))
|
||||
end
|
||||
end
|
||||
|
||||
context "when testing formulae require ARM" do
|
||||
it "activates only the ARM runners" do
|
||||
runner_matrix = described_class.new([testball_depender_arm], [], dependent_matrix: false)
|
||||
|
||||
expect(runner_matrix.runners.all?(&:active)).to be(false)
|
||||
expect(runner_matrix.runners.any?(&:active)).to be(true)
|
||||
expect(get_runner_names(runner_matrix)).to eq(get_runner_names(runner_matrix, :arm64?))
|
||||
end
|
||||
end
|
||||
|
||||
context "when testing formulae require a macOS version" do
|
||||
it "activates the Linux runner and suitable macOS runners" do
|
||||
_, v = newest_supported_macos
|
||||
runner_matrix = described_class.new([testball_depender_newest], [], dependent_matrix: false)
|
||||
|
||||
expect(runner_matrix.runners.all?(&:active)).to be(false)
|
||||
expect(runner_matrix.runners.any?(&:active)).to be(true)
|
||||
expect(get_runner_names(runner_matrix).sort).to eq(["Linux", "macOS #{v}-arm64", "macOS #{v}-x86_64"])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "when it is a matrix for the `test_deps` job" do
|
||||
context "when testing formulae have no dependents" do
|
||||
it "activates no runners" do
|
||||
allow(Homebrew::EnvConfig).to receive(:eval_all?).and_return(true)
|
||||
allow(Formula).to receive(:all).and_return([testball].map(&:formula))
|
||||
|
||||
expect(described_class.new([testball], [], dependent_matrix: true).runners.any?(&:active))
|
||||
.to be(false)
|
||||
end
|
||||
end
|
||||
|
||||
context "when testing formulae have dependents" do
|
||||
context "when dependents have no requirements" do
|
||||
it "activates all runners" do
|
||||
allow(Homebrew::EnvConfig).to receive(:eval_all?).and_return(true)
|
||||
allow(Formula).to receive(:all).and_return([testball, testball_depender].map(&:formula))
|
||||
|
||||
expect(described_class.new([testball], [], dependent_matrix: true).runners.all?(&:active))
|
||||
.to be(true)
|
||||
end
|
||||
end
|
||||
|
||||
context "when dependents require Linux" do
|
||||
it "activates only Linux runners" do
|
||||
allow(Homebrew::EnvConfig).to receive(:eval_all?).and_return(true)
|
||||
allow(Formula).to receive(:all).and_return([testball, testball_depender_linux].map(&:formula))
|
||||
|
||||
runner_matrix = described_class.new([testball], [], dependent_matrix: true)
|
||||
expect(runner_matrix.runners.all?(&:active)).to be(false)
|
||||
expect(runner_matrix.runners.any?(&:active)).to be(true)
|
||||
expect(get_runner_names(runner_matrix)).to eq(get_runner_names(runner_matrix, :linux?))
|
||||
end
|
||||
end
|
||||
|
||||
context "when dependents require macOS" do
|
||||
it "activates only macOS runners" do
|
||||
allow(Homebrew::EnvConfig).to receive(:eval_all?).and_return(true)
|
||||
allow(Formula).to receive(:all).and_return([testball, testball_depender_macos].map(&:formula))
|
||||
|
||||
runner_matrix = described_class.new([testball], [], dependent_matrix: true)
|
||||
expect(runner_matrix.runners.all?(&:active)).to be(false)
|
||||
expect(runner_matrix.runners.any?(&:active)).to be(true)
|
||||
expect(get_runner_names(runner_matrix)).to eq(get_runner_names(runner_matrix, :macos?))
|
||||
end
|
||||
end
|
||||
|
||||
context "when dependents require an Intel architecture" do
|
||||
it "activates only Intel runners" do
|
||||
allow(Homebrew::EnvConfig).to receive(:eval_all?).and_return(true)
|
||||
allow(Formula).to receive(:all).and_return([testball, testball_depender_intel].map(&:formula))
|
||||
|
||||
runner_matrix = described_class.new([testball], [], dependent_matrix: true)
|
||||
expect(runner_matrix.runners.all?(&:active)).to be(false)
|
||||
expect(runner_matrix.runners.any?(&:active)).to be(true)
|
||||
expect(get_runner_names(runner_matrix)).to eq(get_runner_names(runner_matrix, :x86_64?))
|
||||
end
|
||||
end
|
||||
|
||||
context "when dependents require an ARM architecture" do
|
||||
it "activates only ARM runners" do
|
||||
allow(Homebrew::EnvConfig).to receive(:eval_all?).and_return(true)
|
||||
allow(Formula).to receive(:all).and_return([testball, testball_depender_arm].map(&:formula))
|
||||
|
||||
runner_matrix = described_class.new([testball], [], dependent_matrix: true)
|
||||
expect(runner_matrix.runners.all?(&:active)).to be(false)
|
||||
expect(runner_matrix.runners.any?(&:active)).to be(true)
|
||||
expect(get_runner_names(runner_matrix)).to eq(get_runner_names(runner_matrix, :arm64?))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "when there are deleted formulae" do
|
||||
context "when it is a matrix for the `tests` job" do
|
||||
it "activates all runners" do
|
||||
expect(described_class.new([], ["deleted"], dependent_matrix: false).runners.all?(&:active))
|
||||
.to be(true)
|
||||
end
|
||||
end
|
||||
|
||||
context "when it is a matrix for the `test_deps` job" do
|
||||
context "when there are no testing formulae" do
|
||||
it "activates no runners" do
|
||||
expect(described_class.new([], ["deleted"], dependent_matrix: true).runners.any?(&:active))
|
||||
.to be(false)
|
||||
end
|
||||
end
|
||||
|
||||
context "when there are testing formulae with no dependents" do
|
||||
it "activates no runners" do
|
||||
testing_formulae = [testball]
|
||||
runner_matrix = described_class.new(testing_formulae, ["deleted"], dependent_matrix: true)
|
||||
|
||||
allow(Homebrew::EnvConfig).to receive(:eval_all?).and_return(true)
|
||||
allow(Formula).to receive(:all).and_return(testing_formulae.map(&:formula))
|
||||
|
||||
expect(runner_matrix.runners.none?(&:active)).to be(true)
|
||||
end
|
||||
end
|
||||
|
||||
context "when there are testing formulae with dependents" do
|
||||
context "when dependent formulae have no requirements" do
|
||||
it "activates the applicable runners" do
|
||||
allow(Homebrew::EnvConfig).to receive(:eval_all?).and_return(true)
|
||||
allow(Formula).to receive(:all).and_return([testball, testball_depender].map(&:formula))
|
||||
|
||||
testing_formulae = [testball]
|
||||
expect(described_class.new(testing_formulae, ["deleted"], dependent_matrix: true).runners.all?(&:active))
|
||||
.to be(true)
|
||||
end
|
||||
end
|
||||
|
||||
context "when dependent formulae have requirements" do
|
||||
context "when dependent formulae require Linux" do
|
||||
it "activates the applicable runners" do
|
||||
allow(Homebrew::EnvConfig).to receive(:eval_all?).and_return(true)
|
||||
allow(Formula).to receive(:all).and_return([testball, testball_depender_linux].map(&:formula))
|
||||
|
||||
testing_formulae = [testball]
|
||||
matrix = described_class.new(testing_formulae, ["deleted"], dependent_matrix: true)
|
||||
expect(get_runner_names(matrix)).to eq(["Linux"])
|
||||
end
|
||||
end
|
||||
|
||||
context "when dependent formulae require macOS" do
|
||||
it "activates the applicable runners" do
|
||||
allow(Homebrew::EnvConfig).to receive(:eval_all?).and_return(true)
|
||||
allow(Formula).to receive(:all).and_return([testball, testball_depender_macos].map(&:formula))
|
||||
|
||||
testing_formulae = [testball]
|
||||
matrix = described_class.new(testing_formulae, ["deleted"], dependent_matrix: true)
|
||||
expect(get_runner_names(matrix)).to eq(get_runner_names(matrix, :macos?))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def get_runner_names(runner_matrix, predicate = :active)
|
||||
runner_matrix.runners
|
||||
.select(&predicate)
|
||||
.map(&:spec)
|
||||
.map(&:name)
|
||||
end
|
||||
|
||||
def setup_test_runner_formula(name, dependencies = [], **kwargs)
|
||||
f = formula name do
|
||||
url "https://brew.sh/#{name}-1.0.tar.gz"
|
||||
dependencies.each { |dependency| depends_on dependency }
|
||||
|
||||
kwargs.each do |k, v|
|
||||
send(:"on_#{k}") do
|
||||
v.each do |dep|
|
||||
depends_on dep
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
TestRunnerFormula.new(f)
|
||||
end
|
||||
end
|
||||
34
Library/Homebrew/test/github_runner_spec.rb
Normal file
34
Library/Homebrew/test/github_runner_spec.rb
Normal file
@ -0,0 +1,34 @@
|
||||
# typed: false
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "github_runner"
|
||||
|
||||
describe GitHubRunner do
|
||||
let(:runner) do
|
||||
spec = MacOSRunnerSpec.new(name: "macOS 11-arm64", runner: "11-arm64", cleanup: true)
|
||||
version = OS::Mac::Version.new("11")
|
||||
described_class.new(platform: :macos, arch: :arm64, spec: spec, macos_version: version)
|
||||
end
|
||||
|
||||
it "has immutable attributes" do
|
||||
[:platform, :arch, :spec, :macos_version].each do |attribute|
|
||||
expect(runner.respond_to?("#{attribute}=")).to be(false)
|
||||
end
|
||||
end
|
||||
|
||||
it "is inactive by default" do
|
||||
expect(runner.active).to be(false)
|
||||
end
|
||||
|
||||
describe "#macos?" do
|
||||
it "returns true if the runner is a macOS runner" do
|
||||
expect(runner.macos?).to be(true)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#linux?" do
|
||||
it "returns false if the runner is a macOS runner" do
|
||||
expect(runner.linux?).to be(false)
|
||||
end
|
||||
end
|
||||
end
|
||||
29
Library/Homebrew/test/linux_runner_spec_spec.rb
Normal file
29
Library/Homebrew/test/linux_runner_spec_spec.rb
Normal file
@ -0,0 +1,29 @@
|
||||
# typed: false
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "linux_runner_spec"
|
||||
|
||||
describe LinuxRunnerSpec do
|
||||
let(:spec) do
|
||||
described_class.new(
|
||||
name: "Linux",
|
||||
runner: "ubuntu-latest",
|
||||
container: { image: "ghcr.io/homebrew/ubuntu22.04:master", options: "--user=linuxbrew" },
|
||||
workdir: "/github/home",
|
||||
timeout: 360,
|
||||
cleanup: false,
|
||||
)
|
||||
end
|
||||
|
||||
it "has immutable attributes" do
|
||||
[:name, :runner, :container, :workdir, :timeout, :cleanup].each do |attribute|
|
||||
expect(spec.respond_to?("#{attribute}=")).to be(false)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#to_h" do
|
||||
it "returns an object that responds to `#to_json`" do
|
||||
expect(spec.to_h.respond_to?(:to_json)).to be(true)
|
||||
end
|
||||
end
|
||||
end
|
||||
20
Library/Homebrew/test/macos_runner_spec_spec.rb
Normal file
20
Library/Homebrew/test/macos_runner_spec_spec.rb
Normal file
@ -0,0 +1,20 @@
|
||||
# typed: false
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "macos_runner_spec"
|
||||
|
||||
describe MacOSRunnerSpec do
|
||||
let(:spec) { described_class.new(name: "macOS 11-arm64", runner: "11-arm64", cleanup: true) }
|
||||
|
||||
it "has immutable attributes" do
|
||||
[:name, :runner, :cleanup].each do |attribute|
|
||||
expect(spec.respond_to?("#{attribute}=")).to be(false)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#to_h" do
|
||||
it "returns an object that responds to `#to_json`" do
|
||||
expect(spec.to_h.respond_to?(:to_json)).to be(true)
|
||||
end
|
||||
end
|
||||
end
|
||||
467
Library/Homebrew/test/test_runner_formula_spec.rb
Normal file
467
Library/Homebrew/test/test_runner_formula_spec.rb
Normal file
@ -0,0 +1,467 @@
|
||||
# typed: false
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "test_runner_formula"
|
||||
require "test/support/fixtures/testball"
|
||||
|
||||
describe TestRunnerFormula do
|
||||
let(:testball) { Testball.new }
|
||||
let(:xcode_helper) { setup_test_formula("xcode-helper", [:macos]) }
|
||||
let(:linux_kernel_requirer) { setup_test_formula("linux-kernel-requirer", [:linux]) }
|
||||
let(:old_non_portable_software) { setup_test_formula("old-non-portable-software", [arch: :x86_64]) }
|
||||
let(:fancy_new_software) { setup_test_formula("fancy-new-software", [arch: :arm64]) }
|
||||
let(:needs_modern_compiler) { setup_test_formula("needs-modern-compiler", [macos: :ventura]) }
|
||||
|
||||
describe "#initialize" do
|
||||
it "enables the Formulary factory cache" do
|
||||
described_class.new(testball)
|
||||
expect(Formulary.factory_cached?).to be(true)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#name" do
|
||||
it "returns the wrapped Formula's name" do
|
||||
expect(described_class.new(testball).name).to eq(testball.name)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#eval_all" do
|
||||
it "is false by default" do
|
||||
expect(described_class.new(testball).eval_all).to be(false)
|
||||
end
|
||||
|
||||
it "can be instantiated to be `true`" do
|
||||
expect(described_class.new(testball, eval_all: true).eval_all).to be(true)
|
||||
end
|
||||
|
||||
it "takes the value of `HOMEBREW_EVAL_ALL` at instantiation time if not specified" do
|
||||
allow(Homebrew::EnvConfig).to receive(:eval_all?).and_return(true)
|
||||
expect(described_class.new(testball).eval_all).to be(true)
|
||||
|
||||
allow(Homebrew::EnvConfig).to receive(:eval_all?).and_return(false)
|
||||
expect(described_class.new(testball).eval_all).to be(false)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#formula" do
|
||||
it "returns the wrapped Formula" do
|
||||
expect(described_class.new(testball).formula).to eq(testball)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#macos_only?" do
|
||||
context "when a formula requires macOS" do
|
||||
it "returns true" do
|
||||
expect(described_class.new(xcode_helper).macos_only?).to be(true)
|
||||
end
|
||||
end
|
||||
|
||||
context "when a formula does not require macOS" do
|
||||
it "returns false" do
|
||||
expect(described_class.new(testball).macos_only?).to be(false)
|
||||
expect(described_class.new(linux_kernel_requirer).macos_only?).to be(false)
|
||||
expect(described_class.new(old_non_portable_software).macos_only?).to be(false)
|
||||
expect(described_class.new(fancy_new_software).macos_only?).to be(false)
|
||||
end
|
||||
end
|
||||
|
||||
context "when a formula requires only a minimum version of macOS" do
|
||||
it "returns false" do
|
||||
expect(described_class.new(needs_modern_compiler).macos_only?).to be(false)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#macos_compatible?" do
|
||||
context "when a formula is compatible with macOS" do
|
||||
it "returns true" do
|
||||
expect(described_class.new(testball).macos_compatible?).to be(true)
|
||||
expect(described_class.new(xcode_helper).macos_compatible?).to be(true)
|
||||
expect(described_class.new(old_non_portable_software).macos_compatible?).to be(true)
|
||||
expect(described_class.new(fancy_new_software).macos_compatible?).to be(true)
|
||||
end
|
||||
end
|
||||
|
||||
context "when a formula requires only a minimum version of macOS" do
|
||||
it "returns false" do
|
||||
expect(described_class.new(needs_modern_compiler).macos_compatible?).to be(true)
|
||||
end
|
||||
end
|
||||
|
||||
context "when a formula is not compatible with macOS" do
|
||||
it "returns false" do
|
||||
expect(described_class.new(linux_kernel_requirer).macos_compatible?).to be(false)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#linux_only?" do
|
||||
context "when a formula requires Linux" do
|
||||
it "returns true" do
|
||||
expect(described_class.new(linux_kernel_requirer).linux_only?).to be(true)
|
||||
end
|
||||
end
|
||||
|
||||
context "when a formula does not require Linux" do
|
||||
it "returns false" do
|
||||
expect(described_class.new(testball).linux_only?).to be(false)
|
||||
expect(described_class.new(xcode_helper).linux_only?).to be(false)
|
||||
expect(described_class.new(old_non_portable_software).linux_only?).to be(false)
|
||||
expect(described_class.new(fancy_new_software).linux_only?).to be(false)
|
||||
expect(described_class.new(needs_modern_compiler).linux_only?).to be(false)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#linux_compatible?" do
|
||||
context "when a formula is compatible with Linux" do
|
||||
it "returns true" do
|
||||
expect(described_class.new(testball).linux_compatible?).to be(true)
|
||||
expect(described_class.new(linux_kernel_requirer).linux_compatible?).to be(true)
|
||||
expect(described_class.new(old_non_portable_software).linux_compatible?).to be(true)
|
||||
expect(described_class.new(fancy_new_software).linux_compatible?).to be(true)
|
||||
expect(described_class.new(needs_modern_compiler).linux_compatible?).to be(true)
|
||||
end
|
||||
end
|
||||
|
||||
context "when a formula is not compatible with Linux" do
|
||||
it "returns false" do
|
||||
expect(described_class.new(xcode_helper).linux_compatible?).to be(false)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#x86_64_only?" do
|
||||
context "when a formula requires an Intel architecture" do
|
||||
it "returns true" do
|
||||
expect(described_class.new(old_non_portable_software).x86_64_only?).to be(true)
|
||||
end
|
||||
end
|
||||
|
||||
context "when a formula requires a non-Intel architecture" do
|
||||
it "returns false" do
|
||||
expect(described_class.new(fancy_new_software).x86_64_only?).to be(false)
|
||||
end
|
||||
end
|
||||
|
||||
context "when a formula does not require a specfic architecture" do
|
||||
it "returns false" do
|
||||
expect(described_class.new(testball).x86_64_only?).to be(false)
|
||||
expect(described_class.new(xcode_helper).x86_64_only?).to be(false)
|
||||
expect(described_class.new(linux_kernel_requirer).x86_64_only?).to be(false)
|
||||
expect(described_class.new(needs_modern_compiler).x86_64_only?).to be(false)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#x86_64_compatible?" do
|
||||
context "when a formula is compatible with the Intel architecture" do
|
||||
it "returns true" do
|
||||
expect(described_class.new(testball).x86_64_compatible?).to be(true)
|
||||
expect(described_class.new(xcode_helper).x86_64_compatible?).to be(true)
|
||||
expect(described_class.new(linux_kernel_requirer).x86_64_compatible?).to be(true)
|
||||
expect(described_class.new(old_non_portable_software).x86_64_compatible?).to be(true)
|
||||
expect(described_class.new(needs_modern_compiler).x86_64_compatible?).to be(true)
|
||||
end
|
||||
end
|
||||
|
||||
context "when a formula is not compatible with the Intel architecture" do
|
||||
it "returns false" do
|
||||
expect(described_class.new(fancy_new_software).x86_64_compatible?).to be(false)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#arm64_only?" do
|
||||
context "when a formula requires an ARM64 architecture" do
|
||||
it "returns true" do
|
||||
expect(described_class.new(fancy_new_software).arm64_only?).to be(true)
|
||||
end
|
||||
end
|
||||
|
||||
context "when a formula requires a non-ARM64 architecture" do
|
||||
it "returns false" do
|
||||
expect(described_class.new(old_non_portable_software).arm64_only?).to be(false)
|
||||
end
|
||||
end
|
||||
|
||||
context "when a formula does not require a specific architecture" do
|
||||
it "returns false" do
|
||||
expect(described_class.new(testball).arm64_only?).to be(false)
|
||||
expect(described_class.new(xcode_helper).arm64_only?).to be(false)
|
||||
expect(described_class.new(linux_kernel_requirer).arm64_only?).to be(false)
|
||||
expect(described_class.new(needs_modern_compiler).arm64_only?).to be(false)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#arm64_compatible?" do
|
||||
context "when a formula is compatible with an ARM64 architecture" do
|
||||
it "returns true" do
|
||||
expect(described_class.new(testball).arm64_compatible?).to be(true)
|
||||
expect(described_class.new(xcode_helper).arm64_compatible?).to be(true)
|
||||
expect(described_class.new(linux_kernel_requirer).arm64_compatible?).to be(true)
|
||||
expect(described_class.new(fancy_new_software).arm64_compatible?).to be(true)
|
||||
expect(described_class.new(needs_modern_compiler).arm64_compatible?).to be(true)
|
||||
end
|
||||
end
|
||||
|
||||
context "when a formula is not compatible with an ARM64 architecture" do
|
||||
it "returns false" do
|
||||
expect(described_class.new(old_non_portable_software).arm64_compatible?).to be(false)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#versioned_macos_requirement" do
|
||||
let(:requirement) { described_class.new(needs_modern_compiler).versioned_macos_requirement }
|
||||
|
||||
it "returns a MacOSRequirement with a specified version" do
|
||||
expect(requirement).to be_a(MacOSRequirement)
|
||||
expect(requirement.version_specified?).to be(true)
|
||||
end
|
||||
|
||||
context "when a formula has an unversioned MacOSRequirement" do
|
||||
it "returns nil" do
|
||||
expect(described_class.new(xcode_helper).versioned_macos_requirement).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context "when a formula has no declared MacOSRequirement" do
|
||||
it "returns nil" do
|
||||
expect(described_class.new(testball).versioned_macos_requirement).to be_nil
|
||||
expect(described_class.new(linux_kernel_requirer).versioned_macos_requirement).to be_nil
|
||||
expect(described_class.new(old_non_portable_software).versioned_macos_requirement).to be_nil
|
||||
expect(described_class.new(fancy_new_software).versioned_macos_requirement).to be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#compatible_with?" do
|
||||
context "when a formula has a versioned MacOSRequirement" do
|
||||
context "when passed a compatible macOS version" do
|
||||
it "returns true" do
|
||||
expect(described_class.new(needs_modern_compiler).compatible_with?(OS::Mac::Version.new("13")))
|
||||
.to be(true)
|
||||
end
|
||||
end
|
||||
|
||||
context "when passed an incompatible macOS version" do
|
||||
it "returns false" do
|
||||
expect(described_class.new(needs_modern_compiler).compatible_with?(OS::Mac::Version.new("11")))
|
||||
.to be(false)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "when a formula has an unversioned MacOSRequirement" do
|
||||
it "returns true" do
|
||||
MacOSVersions::SYMBOLS.each_value do |v|
|
||||
version = OS::Mac::Version.new(v)
|
||||
expect(described_class.new(xcode_helper).compatible_with?(version)).to be(true)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "when a formula has no declared MacOSRequirement" do
|
||||
it "returns true" do
|
||||
MacOSVersions::SYMBOLS.each_value do |v|
|
||||
version = OS::Mac::Version.new(v)
|
||||
expect(described_class.new(testball).compatible_with?(version)).to be(true)
|
||||
expect(described_class.new(linux_kernel_requirer).compatible_with?(version)).to be(true)
|
||||
expect(described_class.new(old_non_portable_software).compatible_with?(version)).to be(true)
|
||||
expect(described_class.new(fancy_new_software).compatible_with?(version)).to be(true)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#dependents" do
|
||||
let(:current_system) do
|
||||
current_arch = case Homebrew::SimulateSystem.current_arch
|
||||
when :arm then :arm64
|
||||
when :intel then :x86_64
|
||||
end
|
||||
|
||||
current_platform = case Homebrew::SimulateSystem.current_os
|
||||
when :generic then :linux
|
||||
else Homebrew::SimulateSystem.current_os
|
||||
end
|
||||
|
||||
{
|
||||
platform: current_platform,
|
||||
arch: current_arch,
|
||||
macos_version: nil,
|
||||
}
|
||||
end
|
||||
|
||||
context "when a formula has no dependents" do
|
||||
it "returns an empty array" do
|
||||
expect(described_class.new(testball).dependents(current_system)).to eq([])
|
||||
expect(described_class.new(xcode_helper).dependents(current_system)).to eq([])
|
||||
expect(described_class.new(linux_kernel_requirer).dependents(current_system)).to eq([])
|
||||
expect(described_class.new(old_non_portable_software).dependents(current_system)).to eq([])
|
||||
expect(described_class.new(fancy_new_software).dependents(current_system)).to eq([])
|
||||
expect(described_class.new(needs_modern_compiler).dependents(current_system)).to eq([])
|
||||
|
||||
expect(Homebrew::SimulateSystem.os).to be_nil
|
||||
expect(Homebrew::SimulateSystem.arch).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context "when a formula has dependents" do
|
||||
let(:testball_user) { setup_test_formula("testball_user", ["testball"]) }
|
||||
let(:recursive_testball_dependent) { setup_test_formula("recursive_testball_dependent", ["testball_user"]) }
|
||||
|
||||
it "returns an array of direct dependents" do
|
||||
allow(Formula).to receive(:all).and_return([testball_user, recursive_testball_dependent])
|
||||
|
||||
expect(
|
||||
described_class.new(testball, eval_all: true).dependents(current_system).map(&:name),
|
||||
).to eq(["testball_user"])
|
||||
|
||||
expect(
|
||||
described_class.new(testball_user, eval_all: true).dependents(current_system).map(&:name),
|
||||
).to eq(["recursive_testball_dependent"])
|
||||
|
||||
expect(Homebrew::SimulateSystem.os).to be_nil
|
||||
expect(Homebrew::SimulateSystem.arch).to be_nil
|
||||
end
|
||||
|
||||
context "when called with arguments" do
|
||||
let(:testball_user_intel) { setup_test_formula("testball_user-intel", intel: ["testball"]) }
|
||||
let(:testball_user_arm) { setup_test_formula("testball_user-arm", arm: ["testball"]) }
|
||||
let(:testball_user_macos) { setup_test_formula("testball_user-macos", macos: ["testball"]) }
|
||||
let(:testball_user_linux) { setup_test_formula("testball_user-linux", linux: ["testball"]) }
|
||||
let(:testball_user_ventura) do
|
||||
setup_test_formula("testball_user-ventura", ventura: ["testball"])
|
||||
end
|
||||
let(:testball_and_dependents) do
|
||||
[
|
||||
testball_user,
|
||||
testball_user_intel,
|
||||
testball_user_arm,
|
||||
testball_user_macos,
|
||||
testball_user_linux,
|
||||
testball_user_ventura,
|
||||
]
|
||||
end
|
||||
|
||||
context "when given { platform: :linux, arch: :x86_64 }" do
|
||||
before do
|
||||
Homebrew::SimulateSystem.os = :linux
|
||||
Homebrew::SimulateSystem.arch = :intel
|
||||
end
|
||||
|
||||
it "returns only the dependents for the requested platform and architecture" do
|
||||
allow(Formula).to receive(:all).and_return(testball_and_dependents)
|
||||
|
||||
expect(
|
||||
described_class.new(testball, eval_all: true).dependents(
|
||||
platform: :linux, arch: :x86_64, macos_version: nil,
|
||||
).map(&:name).sort,
|
||||
).to eq(["testball_user", "testball_user-intel", "testball_user-linux"].sort)
|
||||
|
||||
expect(Homebrew::SimulateSystem.os).to be_nil
|
||||
expect(Homebrew::SimulateSystem.arch).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context "when given { platform: :macos, arch: :x86_64 }" do
|
||||
before do
|
||||
Homebrew::SimulateSystem.os = :macos
|
||||
Homebrew::SimulateSystem.arch = :intel
|
||||
end
|
||||
|
||||
it "returns only the dependents for the requested platform and architecture" do
|
||||
allow(Formula).to receive(:all).and_return(testball_and_dependents)
|
||||
|
||||
expect(
|
||||
described_class.new(testball, eval_all: true).dependents(
|
||||
platform: :macos, arch: :x86_64, macos_version: nil,
|
||||
).map(&:name).sort,
|
||||
).to eq(["testball_user", "testball_user-intel", "testball_user-macos"].sort)
|
||||
|
||||
expect(Homebrew::SimulateSystem.os).to be_nil
|
||||
expect(Homebrew::SimulateSystem.arch).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context "when given `{ platform: :macos, arch: :arm64 }`" do
|
||||
before do
|
||||
Homebrew::SimulateSystem.os = :macos
|
||||
Homebrew::SimulateSystem.arch = :arm
|
||||
end
|
||||
|
||||
it "returns only the dependents for the requested platform and architecture" do
|
||||
allow(Formula).to receive(:all).and_return(testball_and_dependents)
|
||||
|
||||
expect(
|
||||
described_class.new(testball, eval_all: true).dependents(
|
||||
platform: :macos, arch: :arm64, macos_version: nil,
|
||||
).map(&:name).sort,
|
||||
).to eq(["testball_user", "testball_user-arm", "testball_user-macos"].sort)
|
||||
|
||||
expect(Homebrew::SimulateSystem.os).to be_nil
|
||||
expect(Homebrew::SimulateSystem.arch).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context "when given `{ platform: :macos, arch: :x86_64, macos_version: :mojave }`" do
|
||||
before do
|
||||
Homebrew::SimulateSystem.os = :mojave
|
||||
Homebrew::SimulateSystem.arch = :intel
|
||||
end
|
||||
|
||||
it "returns only the dependents for the requested platform and architecture" do
|
||||
allow(Formula).to receive(:all).and_return(testball_and_dependents)
|
||||
|
||||
expect(
|
||||
described_class.new(testball, eval_all: true).dependents(
|
||||
platform: :macos, arch: :x86_64, macos_version: :mojave,
|
||||
).map(&:name).sort,
|
||||
).to eq(["testball_user", "testball_user-intel", "testball_user-macos"].sort)
|
||||
|
||||
expect(Homebrew::SimulateSystem.os).to be_nil
|
||||
expect(Homebrew::SimulateSystem.arch).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context "when given `{ platform: :macos, arch: :arm64, macos_version: :ventura }`" do
|
||||
before do
|
||||
Homebrew::SimulateSystem.os = :ventura
|
||||
Homebrew::SimulateSystem.arch = :arm
|
||||
end
|
||||
|
||||
it "returns only the dependents for the requested platform and architecture" do
|
||||
allow(Formula).to receive(:all).and_return(testball_and_dependents)
|
||||
|
||||
expect(
|
||||
described_class.new(testball, eval_all: true).dependents(
|
||||
platform: :macos, arch: :arm64, macos_version: :ventura,
|
||||
).map(&:name).sort,
|
||||
).to eq(%w[testball_user testball_user-arm testball_user-macos testball_user-ventura].sort)
|
||||
|
||||
expect(Homebrew::SimulateSystem.os).to be_nil
|
||||
expect(Homebrew::SimulateSystem.arch).to be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def setup_test_formula(name, dependencies = [], **kwargs)
|
||||
formula name do
|
||||
url "https://brew.sh/#{name}-1.0.tar.gz"
|
||||
dependencies.each { |dependency| depends_on dependency }
|
||||
|
||||
kwargs.each do |k, v|
|
||||
send(:"on_#{k}") do
|
||||
v.each do |dep|
|
||||
depends_on dep
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
116
Library/Homebrew/test_runner_formula.rb
Normal file
116
Library/Homebrew/test_runner_formula.rb
Normal file
@ -0,0 +1,116 @@
|
||||
# typed: strict
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "formula"
|
||||
|
||||
class TestRunnerFormula
|
||||
extend T::Sig
|
||||
|
||||
sig { returns(String) }
|
||||
attr_reader :name
|
||||
|
||||
sig { returns(Formula) }
|
||||
attr_reader :formula
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
attr_reader :eval_all
|
||||
|
||||
sig { params(formula: Formula, eval_all: T::Boolean).void }
|
||||
def initialize(formula, eval_all: Homebrew::EnvConfig.eval_all?)
|
||||
Formulary.enable_factory_cache!
|
||||
@formula = T.let(formula, Formula)
|
||||
@name = T.let(formula.name, String)
|
||||
@dependent_hash = T.let({}, T::Hash[Symbol, T::Array[TestRunnerFormula]])
|
||||
@eval_all = T.let(eval_all, T::Boolean)
|
||||
freeze
|
||||
end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def macos_only?
|
||||
formula.requirements.any? { |r| r.is_a?(MacOSRequirement) && !r.version_specified? }
|
||||
end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def macos_compatible?
|
||||
!linux_only?
|
||||
end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def linux_only?
|
||||
formula.requirements.any?(LinuxRequirement)
|
||||
end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def linux_compatible?
|
||||
!macos_only?
|
||||
end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def x86_64_only?
|
||||
formula.requirements.any? { |r| r.is_a?(ArchRequirement) && (r.arch == :x86_64) }
|
||||
end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def x86_64_compatible?
|
||||
!arm64_only?
|
||||
end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def arm64_only?
|
||||
formula.requirements.any? { |r| r.is_a?(ArchRequirement) && (r.arch == :arm64) }
|
||||
end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def arm64_compatible?
|
||||
!x86_64_only?
|
||||
end
|
||||
|
||||
sig { returns(T.nilable(MacOSRequirement)) }
|
||||
def versioned_macos_requirement
|
||||
formula.requirements.find { |r| r.is_a?(MacOSRequirement) && r.version_specified? }
|
||||
end
|
||||
|
||||
sig { params(macos_version: OS::Mac::Version).returns(T::Boolean) }
|
||||
def compatible_with?(macos_version)
|
||||
# Assign to a variable to assist type-checking.
|
||||
requirement = versioned_macos_requirement
|
||||
return true if requirement.blank?
|
||||
|
||||
macos_version.public_send(requirement.comparator, requirement.version)
|
||||
end
|
||||
|
||||
SIMULATE_SYSTEM_SYMBOLS = T.let({ arm64: :arm, x86_64: :intel }.freeze, T::Hash[Symbol, Symbol])
|
||||
|
||||
sig {
|
||||
params(
|
||||
platform: Symbol,
|
||||
arch: Symbol,
|
||||
macos_version: T.nilable(Symbol),
|
||||
).returns(T::Array[TestRunnerFormula])
|
||||
}
|
||||
def dependents(platform:, arch:, macos_version:)
|
||||
cache_key = :"#{platform}_#{arch}_#{macos_version}"
|
||||
|
||||
@dependent_hash.fetch(cache_key) do
|
||||
all = eval_all || Homebrew::EnvConfig.eval_all?
|
||||
formula_selector, eval_all_env = if all
|
||||
[:all, "1"]
|
||||
else
|
||||
[:installed, nil]
|
||||
end
|
||||
|
||||
with_env(HOMEBREW_EVAL_ALL: eval_all_env) do
|
||||
Formulary.clear_cache
|
||||
Homebrew::SimulateSystem.arch = SIMULATE_SYSTEM_SYMBOLS.fetch(arch)
|
||||
Homebrew::SimulateSystem.os = macos_version || platform
|
||||
|
||||
Formula.public_send(formula_selector)
|
||||
.select { |candidate_f| candidate_f.deps.map(&:name).include?(name) }
|
||||
.map { |f| TestRunnerFormula.new(f, eval_all: all) }
|
||||
.freeze
|
||||
ensure
|
||||
Homebrew::SimulateSystem.clear
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -787,6 +787,24 @@ _brew_desc() {
|
||||
__brew_complete_casks
|
||||
}
|
||||
|
||||
_brew_determine_test_runners() {
|
||||
local cur="${COMP_WORDS[COMP_CWORD]}"
|
||||
case "${cur}" in
|
||||
-*)
|
||||
__brewcomp "
|
||||
--debug
|
||||
--dependents
|
||||
--eval-all
|
||||
--help
|
||||
--quiet
|
||||
--verbose
|
||||
"
|
||||
return
|
||||
;;
|
||||
*) ;;
|
||||
esac
|
||||
}
|
||||
|
||||
_brew_developer() {
|
||||
local cur="${COMP_WORDS[COMP_CWORD]}"
|
||||
case "${cur}" in
|
||||
@ -2627,6 +2645,7 @@ _brew() {
|
||||
create) _brew_create ;;
|
||||
deps) _brew_deps ;;
|
||||
desc) _brew_desc ;;
|
||||
determine-test-runners) _brew_determine_test_runners ;;
|
||||
developer) _brew_developer ;;
|
||||
dispatch-build-bottle) _brew_dispatch_build_bottle ;;
|
||||
docs) _brew_docs ;;
|
||||
|
||||
@ -612,6 +612,15 @@ __fish_brew_complete_arg 'desc; and not __fish_seen_argument -l cask -l casks' -
|
||||
__fish_brew_complete_arg 'desc; and not __fish_seen_argument -l formula -l formulae' -a '(__fish_brew_suggest_casks_all)'
|
||||
|
||||
|
||||
__fish_brew_complete_cmd 'determine-test-runners' 'Determines the runners used to test formulae or their dependents'
|
||||
__fish_brew_complete_arg 'determine-test-runners' -l debug -d 'Display any debugging information'
|
||||
__fish_brew_complete_arg 'determine-test-runners' -l dependents -d 'Determine runners for testing dependents. Requires `--eval-all` or `HOMEBREW_EVAL_ALL`'
|
||||
__fish_brew_complete_arg 'determine-test-runners' -l eval-all -d 'Evaluate all available formulae, whether installed or not, to determine testing dependents'
|
||||
__fish_brew_complete_arg 'determine-test-runners' -l help -d 'Show this message'
|
||||
__fish_brew_complete_arg 'determine-test-runners' -l quiet -d 'Make some output more quiet'
|
||||
__fish_brew_complete_arg 'determine-test-runners' -l verbose -d 'Make some output more verbose'
|
||||
|
||||
|
||||
__fish_brew_complete_cmd 'developer' 'Control Homebrew\'s developer mode'
|
||||
__fish_brew_complete_sub_cmd 'developer' 'state'
|
||||
__fish_brew_complete_sub_cmd 'developer' 'on'
|
||||
|
||||
@ -30,6 +30,7 @@ contributions
|
||||
create
|
||||
deps
|
||||
desc
|
||||
determine-test-runners
|
||||
developer
|
||||
dispatch-build-bottle
|
||||
docs
|
||||
|
||||
@ -158,6 +158,7 @@ __brew_internal_commands() {
|
||||
'create:Generate a formula or, with `--cask`, a cask for the downloadable file at URL and open it in the editor'
|
||||
'deps:Show dependencies for formula'
|
||||
'desc:Display formula'\''s name and one-line description'
|
||||
'determine-test-runners:Determines the runners used to test formulae or their dependents'
|
||||
'developer:Control Homebrew'\''s developer mode'
|
||||
'dispatch-build-bottle:Build bottles for these formulae with GitHub Actions'
|
||||
'docs:Open Homebrew'\''s online documentation (https://docs.brew.sh) in a browser'
|
||||
@ -754,6 +755,17 @@ _brew_desc() {
|
||||
'*::cask:__brew_casks'
|
||||
}
|
||||
|
||||
# brew determine-test-runners
|
||||
_brew_determine_test_runners() {
|
||||
_arguments \
|
||||
'--debug[Display any debugging information]' \
|
||||
'--dependents[Determine runners for testing dependents. Requires `--eval-all` or `HOMEBREW_EVAL_ALL`]' \
|
||||
'--eval-all[Evaluate all available formulae, whether installed or not, to determine testing dependents]' \
|
||||
'--help[Show this message]' \
|
||||
'--quiet[Make some output more quiet]' \
|
||||
'--verbose[Make some output more verbose]'
|
||||
}
|
||||
|
||||
# brew developer
|
||||
_brew_developer() {
|
||||
_arguments \
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user