File: test_helper.rb

package info (click to toggle)
ruby-sentry-ruby-core 5.28.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 672 kB
  • sloc: ruby: 6,118; makefile: 8; sh: 4
file content (129 lines) | stat: -rw-r--r-- 4,559 bytes parent folder | download | duplicates (2)
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
# 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.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

    # 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