From 8d2cac50d7dfdfe3f6729ba2ee92c7a31a001fd0 Mon Sep 17 00:00:00 2001 From: Jon Rowe Date: Wed, 17 Jul 2024 11:39:07 +0100 Subject: [PATCH 1/5] Switch rspec local data to a thread accessor --- lib/rspec/support.rb | 14 ++++++-------- spec/rspec/support_spec.rb | 11 +++++++++++ 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/lib/rspec/support.rb b/lib/rspec/support.rb index 72154e07..36325570 100644 --- a/lib/rspec/support.rb +++ b/lib/rspec/support.rb @@ -1,5 +1,9 @@ # frozen_string_literal: true +class Thread + attr_accessor :__rspec_local_data +end + module RSpec module Support # @api private @@ -91,14 +95,8 @@ 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 - def self.thread_local_data - Thread.current.thread_variable_get(:__rspec) || Thread.current.thread_variable_set(:__rspec, {}) - end - else - def self.thread_local_data - Thread.current[:__rspec] ||= {} - end + def self.thread_local_data + Thread.current.__rspec_local_data ||= {} end # @api private diff --git a/spec/rspec/support_spec.rb b/spec/rspec/support_spec.rb index 997658b1..8321c002 100644 --- a/spec/rspec/support_spec.rb +++ b/spec/rspec/support_spec.rb @@ -204,6 +204,17 @@ def object.some_method end.resume end 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 From 23d86e0ff80d49f47ee26f003c2688bd4ca6d4ee Mon Sep 17 00:00:00 2001 From: Jon Rowe Date: Wed, 17 Jul 2024 12:31:20 +0100 Subject: [PATCH 2/5] Protect against mocking Thread.current --- lib/rspec/support.rb | 6 +++++- spec/rspec/support_spec.rb | 7 +++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/lib/rspec/support.rb b/lib/rspec/support.rb index 36325570..bb59726a 100644 --- a/lib/rspec/support.rb +++ b/lib/rspec/support.rb @@ -2,6 +2,10 @@ class Thread attr_accessor :__rspec_local_data + + class << self + alias __rspec_current_thread current + end end module RSpec @@ -96,7 +100,7 @@ def self.class_of(object) # A single thread local variable so we don't excessively pollute that namespace. def self.thread_local_data - Thread.current.__rspec_local_data ||= {} + Thread.__rspec_current_thread.__rspec_local_data ||= {} end # @api private diff --git a/spec/rspec/support_spec.rb b/spec/rspec/support_spec.rb index 8321c002..5d67c4d4 100644 --- a/spec/rspec/support_spec.rb +++ b/spec/rspec/support_spec.rb @@ -205,6 +205,13 @@ def object.some_method 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) From e3d3369ae555b655a1675f2e2c12ef0b1ecfd032 Mon Sep 17 00:00:00 2001 From: Jon Rowe Date: Thu, 18 Jul 2024 09:57:26 +0100 Subject: [PATCH 3/5] Changelog for #610 --- Changelog.md | 5 +++++ 1 file changed, 5 insertions(+) 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) From 1b8a0d207044f0238681251b701d113664aaa921 Mon Sep 17 00:00:00 2001 From: Jon Rowe Date: Sat, 7 Sep 2024 11:53:47 +0100 Subject: [PATCH 4/5] Pend spec broken on JRuby --- spec/rspec/support_spec.rb | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/spec/rspec/support_spec.rb b/spec/rspec/support_spec.rb index 5d67c4d4..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 From d627b9edcc4b20029a4bd0e0f5f1c7590cc35254 Mon Sep 17 00:00:00 2001 From: Jon Rowe Date: Fri, 15 Nov 2024 21:06:14 +0000 Subject: [PATCH 5/5] Restore previous implementation for old Ruby --- lib/rspec/support.rb | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/rspec/support.rb b/lib/rspec/support.rb index bb59726a..70b10351 100644 --- a/lib/rspec/support.rb +++ b/lib/rspec/support.rb @@ -99,8 +99,14 @@ def self.class_of(object) end # A single thread local variable so we don't excessively pollute that namespace. - def self.thread_local_data - Thread.__rspec_current_thread.__rspec_local_data ||= {} + if RUBY_VERSION.to_f != 1.9 + def self.thread_local_data + Thread.__rspec_current_thread.__rspec_local_data ||= {} + end + else + def self.thread_local_data + Thread.current[:__rspec] ||= {} + end end # @api private