Skip to content

TestHelper: setup_sentry_test ineffective in request specs due to clone_hub_to_current_thread overwriting thread-local hub #2951

@minhducch

Description

@minhducch

Description

setup_sentry_test modifies whichever hub Sentry.get_current_hub returns (the thread-local hub), but in Rails request/integration specs, Sentry::Rack::CaptureExceptions middleware calls Sentry.clone_hub_to_current_thread, which always clones @main_hub — not the thread-local hub that setup_sentry_test modified. This overwrites the test setup entirely.

After the first setup_sentry_test call sets a DummyTransport on @main_hub (because in the main thread, thread-local IS @main_hub), teardown_sentry_test pops the scope but the base layer with DummyTransport remains. Subsequent requests in unrelated tests then capture events to this leftover DummyTransport, and those stale events leak into later tests that use sentry_events.

Since RSpec uses random test order, this manifests as intermittent failures.

Reproduction

require "sentry-ruby"
require "sentry/test_helper"
include Sentry::TestHelper

Sentry.init do |config|
  config.dsn = "http://key@sentry.localdomain/42"
  config.environment = "test"
  config.enabled_environments = ["test"]
end

# Test A: first sentry test
setup_sentry_test
teardown_sentry_test

# Test B: intermediate request (no sentry helper)
Sentry.clone_hub_to_current_thread
Sentry.capture_message("leaked event from test B")
puts "main_hub transport events: #{Sentry.get_main_hub.current_client.transport.events.count}"
# => 1

# Test C: new sentry test (should start clean)
setup_sentry_test
puts "sentry_events (thread-local): #{sentry_events.count}"
# => 0 (looks clean)

# Simulate Rack middleware
Sentry.clone_hub_to_current_thread
puts "sentry_events (after clone): #{Sentry.get_current_client.transport.events.count}"
# => 1 (BUG: stale event from Test B leaked through!)

teardown_sentry_test

Output:

main_hub transport events: 1
sentry_events (thread-local): 0
sentry_events (after clone): 1

Expected Behavior

After setup_sentry_test, sentry_events should always be empty, even after clone_hub_to_current_thread is called.

Root Cause

  1. setup_sentry_test calls get_current_hub which returns the thread-local hub (not necessarily @main_hub)
  2. It modifies this hub with DummyTransport
  3. clone_hub_to_current_thread always clones @main_hub, discarding the thread-local hub
  4. Hub#clone creates Hub.new(layer.client, scope.dup) — sharing the same client (and transport) as @main_hub
  5. Stale events on @main_hub's DummyTransport become visible through the clone

Workaround

DummyTransport (as of sentry-ruby 6.5.0) does not implement clear, so events and envelopes arrays must be cleared directly on both the current hub and @main_hub transports:

def clear_sentry_transport(transport)
  return unless transport
  transport.events.clear if transport.respond_to?(:events)
  transport.envelopes.clear if transport.respond_to?(:envelopes)
end

# After setup_sentry_test:
clear_sentry_transport(Sentry.get_current_client&.transport)
clear_sentry_transport(Sentry.get_main_hub&.current_client&.transport)

Both hubs must be cleared because clone_hub_to_current_thread shares the same client (and transport) instance between @main_hub and its clones via Hub#clone.

SDK Version

sentry-ruby 6.5.0

Ruby Version

3.4.9

Metadata

Metadata

Assignees

No one assigned
    No fields configured for issues without a type.

    Projects

    Status

    Waiting for: Product Owner

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions