1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137
|
# frozen_string_literal: true
module Sentry
module TestHelper
module_function
DUMMY_DSN = "http://12345:67890@sentry.localdomain/sentry/42"
# Not really real, but it will be resolved as a non-local for testing needs
REAL_DSN = "https://user:pass@getsentry.io/project/42"
# Alters the existing SDK configuration with test-suitable options. Mainly:
# - Sets a dummy DSN instead of `nil` or an actual DSN.
# - Sets the transport to DummyTransport, which allows easy access to the captured events.
# - Disables background worker.
# - Makes sure the SDK is enabled under the current environment ("test" in most cases).
#
# It should be called **before** every test case.
#
# @yieldparam config [Configuration]
# @return [void]
def setup_sentry_test(&block)
raise "please make sure the SDK is initialized for testing" unless Sentry.initialized?
dummy_config = Sentry.configuration.dup
# configure dummy DSN, so the events will not be sent to the actual service
dummy_config.dsn = DUMMY_DSN
# set transport to DummyTransport, so we can easily intercept the captured events
dummy_config.transport.transport_class = Sentry::DummyTransport
# make sure SDK allows sending under the current environment
dummy_config.enabled_environments ||= []
dummy_config.enabled_environments += [dummy_config.environment] unless dummy_config.enabled_environments.include?(dummy_config.environment)
# disble async event sending
dummy_config.background_worker_threads = 0
# user can overwrite some of the configs, with a few exceptions like:
# - include_local_variables
# - auto_session_tracking
block&.call(dummy_config)
# the base layer's client should already use the dummy config so nothing will be sent by accident
base_client = Sentry::Client.new(dummy_config)
Sentry.get_current_hub.bind_client(base_client)
# create a new layer so mutations made to the testing scope or configuration could be simply popped later
Sentry.get_current_hub.push_scope
test_client = Sentry::Client.new(dummy_config.dup)
Sentry.get_current_hub.bind_client(test_client)
end
# Clears all stored events and envelopes.
# It should be called **after** every test case.
# @return [void]
def teardown_sentry_test
return unless Sentry.initialized?
clear_sentry_events
# pop testing layer created by `setup_sentry_test`
# but keep the base layer to avoid nil-pointer errors
# TODO: find a way to notify users if they somehow popped the test layer before calling this method
if Sentry.get_current_hub.instance_variable_get(:@stack).size > 1
Sentry.get_current_hub.pop_scope
end
Sentry::Scope.global_event_processors.clear
end
def clear_sentry_events
return unless Sentry.initialized?
sentry_transport.clear if sentry_transport.respond_to?(:clear)
if Sentry.configuration.enable_logs && sentry_logger.respond_to?(:clear)
sentry_logger.clear
end
end
# @return [Sentry::StructuredLogger, Sentry::DebugStructuredLogger]
def sentry_logger
Sentry.logger
end
# @return [Transport]
def sentry_transport
Sentry.get_current_client.transport
end
# Returns the captured event objects.
# @return [Array<Event>]
def sentry_events
sentry_transport.events
end
# Returns the captured envelope objects.
# @return [Array<Envelope>]
def sentry_envelopes
sentry_transport.envelopes
end
def sentry_logs
sentry_envelopes
.flat_map(&:items)
.select { |item| item.headers[:type] == "log" }
.flat_map { |item| item.payload[:items] }
end
def sentry_metrics
sentry_envelopes
.flat_map(&:items)
.select { |item| item.headers[:type] == "trace_metric" }
.flat_map { |item| item.payload[:items] }
end
# Returns the last captured event object.
# @return [Event, nil]
def last_sentry_event
sentry_events.last
end
# Extracts SDK's internal exception container (not actual exception objects) from an given event.
# @return [Array<Sentry::SingleExceptionInterface>]
def extract_sentry_exceptions(event)
event&.exception&.values || []
end
def reset_sentry_globals!
Sentry::MUTEX.synchronize do
# Don't check initialized? because sometimes we stub it in tests
if Sentry.instance_variable_defined?(:@main_hub)
Sentry::GLOBALS.each do |var|
Sentry.instance_variable_set(:"@#{var}", nil)
end
Thread.current.thread_variable_set(Sentry::THREAD_LOCAL, nil)
end
end
end
end
end
|