diff --git a/Changelog.md b/Changelog.md index b437f890..73942f0d 100644 --- a/Changelog.md +++ b/Changelog.md @@ -6,6 +6,11 @@ Enchancements * Improve diff for `anything` matchers by hiding the value "anything" is matched against. (Karl Heitmann, #599) +Bug Fixes: + +* Switch current thread data to alias/accessors to avoid issues with mocked systems. + (Jon Rowe, #610) + ### 3.13.1 / 2024-02-23 [Full Changelog](http://github.com/rspec/rspec-support/compare/v3.13.0...v3.13.1) diff --git a/lib/rspec/support.rb b/lib/rspec/support.rb index 72154e07..70b10351 100644 --- a/lib/rspec/support.rb +++ b/lib/rspec/support.rb @@ -1,5 +1,13 @@ # frozen_string_literal: true +class Thread + attr_accessor :__rspec_local_data + + class << self + alias __rspec_current_thread current + end +end + module RSpec module Support # @api private @@ -91,9 +99,9 @@ def self.class_of(object) end # A single thread local variable so we don't excessively pollute that namespace. - if RUBY_VERSION.to_f >= 2 + if RUBY_VERSION.to_f != 1.9 def self.thread_local_data - Thread.current.thread_variable_get(:__rspec) || Thread.current.thread_variable_set(:__rspec, {}) + Thread.__rspec_current_thread.__rspec_local_data ||= {} end else def self.thread_local_data diff --git a/spec/rspec/support_spec.rb b/spec/rspec/support_spec.rb index 997658b1..2d23c373 100644 --- a/spec/rspec/support_spec.rb +++ b/spec/rspec/support_spec.rb @@ -196,7 +196,20 @@ def object.some_method end if defined?(Fiber) && RUBY_VERSION.to_f >= 2.0 - it "shares data across fibres" do + broken_on_jruby = + if RSpec::Support::Ruby.jruby? + "As Fiber.new creates a new thread on JRuby this is currently " \ + "broken. There are alternative implementations that do work but " \ + "they cause issues for mocks, so given this is a minor edge case " \ + "and thread data is already broken, its acceptable. Pending " \ + "because future JRuby may fix this. see: "\ + "https://github.com/jruby/jruby/issues/1806 and " \ + "https://github.com/rspec/rspec-support/pull/610" + else + false + end + + it "shares data across fibers", :pending => broken_on_jruby do RSpec::Support.thread_local_data[:__for_test] = :oh_hai Fiber.new do @@ -204,6 +217,24 @@ def object.some_method end.resume end end + + it "works when Thread.current is mocked" do + expect(Thread).to_not receive(:current) + + RSpec::Support.thread_local_data[:__for_test] = :oh_hai + expect(RSpec::Support.thread_local_data[:__for_test]).to eq :oh_hai + end + + it "works when Thread#thread_variable_get and Thread#thread_variable_set are mocked" do + expect(Thread.current).to receive(:thread_variable_set).with(:test, true).once.and_return(true) + expect(Thread.current).to receive(:thread_variable_get).with(:test).once.and_return(true) + + Thread.current.thread_variable_set(:test, true) + expect(Thread.current.thread_variable_get(:test)).to eq true + + RSpec::Support.thread_local_data[:__for_test] = :oh_hai + expect(RSpec::Support.thread_local_data[:__for_test]).to eq :oh_hai + end end describe "failure notification" do