brew/Library/Homebrew/test/spec_helper.rb
apainintheneck 74aea8e92d spec_helper: add :no_api test scope
This sets the HOMEBREW_NO_INSTALL_FROM_API environment variable
to prevent the selected tests from using the API. We will need
this as we transition to having the API be enabled by default
when running the tests but it's also nice as a sanity check
with the :needs_utils_curl scope in a few places.
2024-03-19 22:18:02 -07:00

330 lines
9.7 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/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 "support/extend/cachable"
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 do |example|
next if example.metadata.key?(:needs_network)
next if example.metadata.key?(:needs_utils_curl)
allow(Utils::Curl).to receive(:curl_executable).and_raise(<<~ERROR)
Unexpected call to Utils::Curl.curl_executable without setting :needs_network or :needs_utils_curl.
ERROR
end
config.before(:each, :no_api) do
ENV["HOMEBREW_NO_INSTALL_FROM_API"] = "1"
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
Tap.installed.each(&:clear_cache)
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
ENV.replace(@__env)
Context.current = Context::ContextStruct.new
$stdout.reopen(@__stdout)
$stderr.reopen(@__stderr)
$stdin.reopen(@__stdin)
@__stdout.close
@__stderr.close
@__stdin.close
Tap.all.each(&:clear_cache)
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.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