diff --git a/Library/Homebrew/download_queue.rb b/Library/Homebrew/download_queue.rb index 076135389c..c0987742c7 100644 --- a/Library/Homebrew/download_queue.rb +++ b/Library/Homebrew/download_queue.rb @@ -559,6 +559,14 @@ module Homebrew @default_download_queue ||= T.let(DownloadQueue.new, T.nilable(DownloadQueue)) end + sig { void } + def self.reset_default_download_queue + # Skip `shutdown` for a leaked RSpec double, which cannot receive + # messages outside the per-example rspec-mocks lifecycle. + @default_download_queue.shutdown if @default_download_queue.is_a?(DownloadQueue) + @default_download_queue = nil + end + sig { void } def self.shutdown_default_download_queue @default_download_queue&.shutdown diff --git a/Library/Homebrew/test/download_queue_spec.rb b/Library/Homebrew/test/download_queue_spec.rb index 54c0bfc9de..7550103dfd 100644 --- a/Library/Homebrew/test/download_queue_spec.rb +++ b/Library/Homebrew/test/download_queue_spec.rb @@ -361,4 +361,24 @@ RSpec.describe Homebrew::DownloadQueue do download_queue.enqueue(bottle, check_attestation: true) download_queue.fetch end + + describe "Homebrew.default_download_queue", order: :defined do + it "memoizes the queue created on first use" do + queue = instance_double(described_class, shutdown: nil) + allow(described_class).to receive(:new).and_return(queue) + + expect(Homebrew.default_download_queue).to be(queue) + end + + it "does not leak a queue stubbed by an earlier example" do + expect(Homebrew.default_download_queue).to be_an_instance_of(described_class) + end + + it "shuts down a memoized real queue when reset" do + queue = Homebrew.default_download_queue + expect(queue).to receive(:shutdown) + + Homebrew.reset_default_download_queue + end + end end diff --git a/Library/Homebrew/test/spec_helper.rb b/Library/Homebrew/test/spec_helper.rb index 2811726f36..6372a3f6a6 100644 --- a/Library/Homebrew/test/spec_helper.rb +++ b/Library/Homebrew/test/spec_helper.rb @@ -364,6 +364,10 @@ RSpec.configure do |config| ENV.replace(@__env) Homebrew::SimulateSystem.clear Context.current = Context::ContextStruct.new + # Shut down and drop any memoized download queue so an example that + # stubbed `DownloadQueue.new` cannot leak a double into later examples + # or the `at_exit` shutdown hook. + Homebrew.reset_default_download_queue if Homebrew.respond_to?(:reset_default_download_queue) $stdout.reopen(@__stdout) $stderr.reopen(@__stderr)