File: test_helper.rb

package info (click to toggle)
ruby-sentry-ruby 6.3.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 644 kB
  • sloc: ruby: 5,771; makefile: 8; sh: 4
file content (137 lines) | stat: -rw-r--r-- 4,803 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
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