Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RSpec leaks ActiveSupport::ExecutionContext between specs. #2644

Closed
BobbyMcWho opened this issue Dec 13, 2022 · 8 comments · Fixed by #2711
Closed

RSpec leaks ActiveSupport::ExecutionContext between specs. #2644

BobbyMcWho opened this issue Dec 13, 2022 · 8 comments · Fixed by #2711

Comments

@BobbyMcWho
Copy link
Contributor

BobbyMcWho commented Dec 13, 2022

What Ruby, Rails and RSpec versions are you using?

Ruby version: 2.7
Rails version: 7.0.4
RSpec version: ~> 6.0

Observed behaviour

When using the Rails 7+ built in error reporter Rails.error, any context set during a spec leaks to the next spec.

I've resolved this by

config.before(:each)
  ActiveSupport::ExecutionContext.clear
end

While this works for me, it feels like a tripping point for folks using rspec-rails on rails >= 7.0

Expected behaviour

ExecutionContext should be cleared between each spec, so that context does not leak between specs.

Can you provide an example app?

require 'bundler/inline'

gemfile(true) do
  source 'https://rubygems.org'
  gem 'rails', '7.0.4'
  gem 'rspec-rails', '~> 6.0.0'
end

require 'rails'
require 'action_controller/railtie'

class MyApplication < Rails::Application; end

class PagesController < ActionController::Base
  include Rails.application.routes.url_helpers

  def index
    render inline: "SOME HTML"
  end
end

MyApplication.routes.draw do
  get '/pages', to: 'pages#index'
end

require 'rspec/rails'
require "rspec/autorun"

def app
  MyApplication
end

RSpec.describe "First non-controller test" do
  it "does not leak the controller context" do
    expect(ActiveSupport::ExecutionContext.to_h).to eq({})
  end
end

RSpec.describe PagesController, type: :controller do
  it "runs a controller test" do
    get :index
    expect(response.body).to eq("SOME HTML")
    expect(ActiveSupport::ExecutionContext.to_h).to eq({ controller: controller })
  end
end

# fails
RSpec.describe "Second non-controller test" do
  it "does not leak the controller context" do
    expect(ActiveSupport::ExecutionContext.to_h).to eq({})
  end
end

RSpec.describe "A context test" do
  before do
    ActiveSupport::ExecutionContext.clear
    Rails.error.set_context(foo: "bar")
  end

  it "sets the context" do
    expect(ActiveSupport::ExecutionContext.to_h).to eq({foo: "bar"})
  end
end

# fails
RSpec.describe "A second context test" do
  it "does not leak the context" do
    expect(ActiveSupport::ExecutionContext.to_h).to eq({})
  end
end
@pirj
Copy link
Member

pirj commented Dec 13, 2022

A quick solution would be to include ActiveSupport::ExecutionContext::TestHelper to RSpec::Rails::RailsExampleGroup.

What is interesting is that a new (can't find it in Rails 6.1 docs) option, executor_around_test_case, which defaults to true since 7.0, basing on which, ActiveSupport::Executor::TestHelper would be included into ActiveSupport::TestCase instead.

Would you be interested in sending a PR, @BobbyMcWho ?

@javierjulio
Copy link
Contributor

@pirj I've been reviewing this for awhile now since I was trying to understand if this would affect our Rails 7 upgrade and to see about contributing a fix/update. But I had been going around in circles trying to understand why this isn't supported and resetting CurrentAttributes is since they are treated the same. I recall some time ago coming across #2503 and with this comment #2503 (comment) it details why some of the tests in this issue description are failing: they aren't specifying a type, e.g. type: :model. I was going to contribute a change but wonder now if this should be closed out instead, unless Rspec wants to support this and other Rails based helpers outside Rails-ish tests? Perhaps there is a special case here with the new Rails.error feature though.

@bensheldon
Copy link

bensheldon commented Oct 18, 2023

I believe that the best option here is reproducing the behavior of ActiveSupport::Executor::TestHelper / config.active_support.executor_around_test_case and wrapping every example by default with Rails.application.executor.perform {}. It's not just ExecutionContext that isn't cleared, but the absence of a wrapping Executor can also alter Active Record behavior and other Rails behavior that expect to always be wrapped within an Executor.

@JonRowe
Copy link
Member

JonRowe commented Nov 16, 2023

Rails.application.executor.perform {} is not actually enough from testing but including the module if it exists works

@JonRowe
Copy link
Member

JonRowe commented Nov 16, 2023

If someone wants to take up the executor thing you can see it currently blows up various job things for us in #2712, given I don't really know what it affects and most internal rails things will have their own I'm inclined to leave it for now

@bensheldon
Copy link

@JonRowe I can take on the Executor wrap, thanks for linking to #2712. I'll make an issue to track it.

I don't really know what it affects and most internal rails things will have their own I'm inclined to leave it for now

In Active Record, it will allow for the query cache to be enabled, it will also appropriately checkout and check-in database connections (and retry/recover from connection errors).

Also, the executor will only be present for integration-like tests because the only places Rails will inserts executors is via a Rack Middleware, or when deserializing a job via perform_later.

@bensheldon
Copy link

Created #2713

@JonRowe
Copy link
Member

JonRowe commented Nov 21, 2023

Fix technically released in 6.0.4 but please use 6.1.0 for Rails 7.1 support.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants