mirror of
https://github.com/Homebrew/brew.git
synced 2025-07-14 16:09:03 +08:00

This adds a registry for all modules and classes that cachable is included in. The registry allows us to programmatically clear all caches in between tests so that we don't forget to do that when adding a new class or refactoring code. The goal here is to reduce the number of flaky tests in the future.
318 lines
9.4 KiB
Ruby
318 lines
9.4 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
if ENV["HOMEBREW_TESTS_COVERAGE"]
|
|
require "simplecov"
|
|
require "simplecov-cobertura"
|
|
|
|
formatters = [
|
|
SimpleCov::Formatter::HTMLFormatter,
|
|
SimpleCov::Formatter::CoberturaFormatter,
|
|
]
|
|
SimpleCov.formatters = SimpleCov::Formatter::MultiFormatter.new(formatters)
|
|
|
|
if RUBY_PLATFORM[/darwin/] && ENV["TEST_ENV_NUMBER"]
|
|
SimpleCov.at_exit do
|
|
result = SimpleCov.result
|
|
result.format! if ParallelTests.number_of_running_processes <= 1
|
|
end
|
|
end
|
|
end
|
|
|
|
require_relative "../warnings"
|
|
|
|
Warnings.ignore :parser_syntax do
|
|
require "rubocop"
|
|
end
|
|
|
|
require "rspec/its"
|
|
require "rspec/github"
|
|
require "rspec/retry"
|
|
require "rspec/sorbet"
|
|
require "rubocop/rspec/support"
|
|
require "find"
|
|
require "byebug"
|
|
require "timeout"
|
|
|
|
$LOAD_PATH.unshift(File.expand_path("#{ENV.fetch("HOMEBREW_LIBRARY")}/Homebrew/test/support/lib"))
|
|
|
|
require_relative "../global"
|
|
|
|
require "test/support/quiet_progress_formatter"
|
|
require "test/support/helper/cask"
|
|
require "test/support/helper/files"
|
|
require "test/support/helper/fixtures"
|
|
require "test/support/helper/formula"
|
|
require "test/support/helper/mktmpdir"
|
|
require "test/support/helper/output_as_tty"
|
|
|
|
require "test/support/helper/spec/shared_context/homebrew_cask" if OS.mac?
|
|
require "test/support/helper/spec/shared_context/integration_test"
|
|
require "test/support/helper/spec/shared_examples/formulae_exist"
|
|
|
|
TEST_DIRECTORIES = [
|
|
CoreTap.instance.path/"Formula",
|
|
HOMEBREW_CACHE,
|
|
HOMEBREW_CACHE_FORMULA,
|
|
HOMEBREW_CELLAR,
|
|
HOMEBREW_LOCKS,
|
|
HOMEBREW_LOGS,
|
|
HOMEBREW_TEMP,
|
|
].freeze
|
|
|
|
# Make `instance_double` and `class_double`
|
|
# work when type-checking is active.
|
|
RSpec::Sorbet.allow_doubles!
|
|
|
|
RSpec.configure do |config|
|
|
config.order = :random
|
|
|
|
config.raise_errors_for_deprecations!
|
|
config.warnings = true
|
|
config.disable_monkey_patching!
|
|
|
|
config.filter_run_when_matching :focus
|
|
|
|
config.silence_filter_announcements = true if ENV["TEST_ENV_NUMBER"]
|
|
|
|
# Improve backtrace formatting
|
|
config.filter_gems_from_backtrace "rspec-retry", "sorbet-runtime"
|
|
config.backtrace_exclusion_patterns << %r{test/spec_helper\.rb}
|
|
|
|
config.expect_with :rspec do |c|
|
|
c.max_formatted_output_length = 200
|
|
end
|
|
|
|
# Use rspec-retry to handle flaky tests.
|
|
config.default_sleep_interval = 1
|
|
|
|
# Don't want the nicer default retry behaviour when using BuildPulse to
|
|
# identify flaky tests.
|
|
config.default_retry_count = 2 unless ENV["BUILDPULSE"]
|
|
|
|
config.expect_with :rspec do |expectations|
|
|
# This option will default to `true` in RSpec 4. It makes the `description`
|
|
# and `failure_message` of custom matchers include text for helper methods
|
|
# defined using `chain`, e.g.:
|
|
# be_bigger_than(2).and_smaller_than(4).description
|
|
# # => "be bigger than 2 and smaller than 4"
|
|
# ...rather than:
|
|
# # => "be bigger than 2"
|
|
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
|
|
end
|
|
config.mock_with :rspec do |mocks|
|
|
# Prevents you from mocking or stubbing a method that does not exist on
|
|
# a real object. This is generally recommended, and will default to
|
|
# `true` in RSpec 4.
|
|
mocks.verify_partial_doubles = true
|
|
end
|
|
config.shared_context_metadata_behavior = :apply_to_host_groups
|
|
|
|
# Increase timeouts for integration tests (as we expect them to take longer).
|
|
config.around(:each, :integration_test) do |example|
|
|
example.metadata[:timeout] ||= 120
|
|
example.run
|
|
end
|
|
|
|
config.around(:each, :needs_network) do |example|
|
|
example.metadata[:timeout] ||= 120
|
|
|
|
# Don't want the nicer default retry behaviour when using BuildPulse to
|
|
# identify flaky tests.
|
|
example.metadata[:retry] ||= 4 unless ENV["BUILDPULSE"]
|
|
|
|
example.metadata[:retry_wait] ||= 2
|
|
example.metadata[:exponential_backoff] ||= true
|
|
example.run
|
|
end
|
|
|
|
# Never truncate output objects.
|
|
RSpec::Support::ObjectFormatter.default_instance.max_formatted_output_length = nil
|
|
|
|
config.include(FileUtils)
|
|
|
|
config.include(Context)
|
|
|
|
config.include(RuboCop::RSpec::ExpectOffense)
|
|
|
|
config.include(Test::Helper::Cask)
|
|
config.include(Test::Helper::Fixtures)
|
|
config.include(Test::Helper::Formula)
|
|
config.include(Test::Helper::MkTmpDir)
|
|
config.include(Test::Helper::OutputAsTTY)
|
|
|
|
config.before(:each, :needs_linux) do
|
|
skip "Not running on Linux." unless OS.linux?
|
|
end
|
|
|
|
config.before(:each, :needs_macos) do
|
|
skip "Not running on macOS." unless OS.mac?
|
|
end
|
|
|
|
config.before(:each, :needs_ci) do
|
|
skip "Not running on CI." unless ENV["CI"]
|
|
end
|
|
|
|
config.before(:each, :needs_java) do
|
|
skip "Java is not installed." unless which("java")
|
|
end
|
|
|
|
config.before(:each, :needs_python) do
|
|
skip "Python is not installed." if !which("python3") && !which("python")
|
|
end
|
|
|
|
config.before(:each, :needs_network) do
|
|
skip "Requires network connection." unless ENV["HOMEBREW_TEST_ONLINE"]
|
|
end
|
|
|
|
config.before(:each, :needs_svn) do
|
|
svn_shim = HOMEBREW_SHIMS_PATH/"shared/svn"
|
|
skip "Subversion is not installed." unless quiet_system svn_shim, "--version"
|
|
|
|
svn_shim_path = Pathname(Utils.popen_read(svn_shim, "--homebrew=print-path").chomp.presence)
|
|
svn_paths = PATH.new(ENV.fetch("PATH"))
|
|
svn_paths.prepend(svn_shim_path.dirname)
|
|
|
|
if OS.mac?
|
|
xcrun_svn = Utils.popen_read("xcrun", "-f", "svn")
|
|
svn_paths.append(File.dirname(xcrun_svn)) if $CHILD_STATUS.success? && xcrun_svn.present?
|
|
end
|
|
|
|
svn = which("svn", svn_paths)
|
|
skip "svn is not installed." unless svn
|
|
|
|
svnadmin = which("svnadmin", svn_paths)
|
|
skip "svnadmin is not installed." unless svnadmin
|
|
|
|
ENV["PATH"] = PATH.new(ENV.fetch("PATH"))
|
|
.append(svn.dirname)
|
|
.append(svnadmin.dirname)
|
|
end
|
|
|
|
config.before(:each, :needs_homebrew_curl) do
|
|
ENV["HOMEBREW_CURL"] = HOMEBREW_BREWED_CURL_PATH
|
|
skip "A `curl` with TLS 1.3 support is required." unless Utils::Curl.curl_supports_tls13?
|
|
rescue FormulaUnavailableError
|
|
skip "No `curl` formula is available."
|
|
end
|
|
|
|
config.before(:each, :needs_unzip) do
|
|
skip "Unzip is not installed." unless which("unzip")
|
|
end
|
|
|
|
config.around do |example|
|
|
Homebrew.raise_deprecation_exceptions = true
|
|
|
|
Cachable::Registry.clear_all_caches
|
|
FormulaInstaller.clear_attempted
|
|
FormulaInstaller.clear_installed
|
|
FormulaInstaller.clear_fetched
|
|
Utils::Curl.clear_path_cache
|
|
|
|
TEST_DIRECTORIES.each(&:mkpath)
|
|
|
|
@__homebrew_failed = Homebrew.failed?
|
|
|
|
@__files_before_test = Test::Helper::Files.find_files
|
|
|
|
@__env = ENV.to_hash # dup doesn't work on ENV
|
|
|
|
@__stdout = $stdout.clone
|
|
@__stderr = $stderr.clone
|
|
@__stdin = $stdin.clone
|
|
|
|
begin
|
|
if !example.metadata.keys.intersect?([:focus, :byebug]) && !ENV.key?("HOMEBREW_VERBOSE_TESTS")
|
|
$stdout.reopen(File::NULL)
|
|
$stderr.reopen(File::NULL)
|
|
else
|
|
# don't retry when focusing/debugging
|
|
config.default_retry_count = 0
|
|
end
|
|
$stdin.reopen(File::NULL)
|
|
|
|
begin
|
|
timeout = example.metadata.fetch(:timeout, 60)
|
|
Timeout.timeout(timeout) do
|
|
example.run
|
|
end
|
|
rescue Timeout::Error => e
|
|
example.example.set_exception(e)
|
|
end
|
|
rescue SystemExit => e
|
|
example.example.set_exception(e)
|
|
ensure
|
|
# This depends on `HOMEBREW_NO_INSTALL_FROM_API`.
|
|
Tap.each(&:clear_cache)
|
|
|
|
ENV.replace(@__env)
|
|
Context.current = Context::ContextStruct.new
|
|
|
|
$stdout.reopen(@__stdout)
|
|
$stderr.reopen(@__stderr)
|
|
$stdin.reopen(@__stdin)
|
|
@__stdout.close
|
|
@__stderr.close
|
|
@__stdin.close
|
|
|
|
Cachable::Registry.clear_all_caches
|
|
|
|
FileUtils.rm_rf [
|
|
*TEST_DIRECTORIES,
|
|
*Keg::MUST_EXIST_SUBDIRECTORIES,
|
|
HOMEBREW_LINKED_KEGS,
|
|
HOMEBREW_PINNED_KEGS,
|
|
HOMEBREW_PREFIX/"var",
|
|
HOMEBREW_PREFIX/"Caskroom",
|
|
HOMEBREW_PREFIX/"Frameworks",
|
|
HOMEBREW_LIBRARY/"Taps/homebrew/homebrew-cask",
|
|
HOMEBREW_LIBRARY/"Taps/homebrew/homebrew-bar",
|
|
HOMEBREW_LIBRARY/"Taps/homebrew/homebrew-bundle",
|
|
HOMEBREW_LIBRARY/"Taps/homebrew/homebrew-foo",
|
|
HOMEBREW_LIBRARY/"Taps/homebrew/homebrew-services",
|
|
HOMEBREW_LIBRARY/"Taps/homebrew/homebrew-shallow",
|
|
HOMEBREW_LIBRARY/"PinnedTaps",
|
|
HOMEBREW_REPOSITORY/".git",
|
|
CoreTap.instance.path/".git",
|
|
CoreTap.instance.alias_dir,
|
|
CoreTap.instance.path/"formula_renames.json",
|
|
CoreTap.instance.path/"tap_migrations.json",
|
|
CoreTap.instance.path/"audit_exceptions",
|
|
CoreTap.instance.path/"style_exceptions",
|
|
CoreTap.instance.path/"pypi_formula_mappings.json",
|
|
*Pathname.glob("#{HOMEBREW_CELLAR}/*/"),
|
|
]
|
|
|
|
files_after_test = Test::Helper::Files.find_files
|
|
|
|
diff = Set.new(@__files_before_test) ^ Set.new(files_after_test)
|
|
expect(diff).to be_empty, <<~EOS
|
|
file leak detected:
|
|
#{diff.map { |f| " #{f}" }.join("\n")}
|
|
EOS
|
|
|
|
Homebrew.failed = @__homebrew_failed
|
|
end
|
|
end
|
|
end
|
|
|
|
RSpec::Matchers.define_negated_matcher :not_to_output, :output
|
|
RSpec::Matchers.define_negated_matcher :not_raise_error, :raise_error
|
|
RSpec::Matchers.alias_matcher :have_failed, :be_failed
|
|
RSpec::Matchers.alias_matcher :a_string_containing, :include
|
|
|
|
RSpec::Matchers.define :a_json_string do
|
|
match do |actual|
|
|
JSON.parse(actual)
|
|
true
|
|
rescue JSON::ParserError
|
|
false
|
|
end
|
|
end
|
|
|
|
# Match consecutive elements in an array.
|
|
RSpec::Matchers.define :array_including_cons do |*cons|
|
|
match do |actual|
|
|
expect(actual.each_cons(cons.size)).to include(cons)
|
|
end
|
|
end
|