airbrake-ruby 4.15.0 → 5.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/lib/airbrake-ruby.rb +22 -33
  3. data/lib/airbrake-ruby/async_sender.rb +1 -1
  4. data/lib/airbrake-ruby/backtrace.rb +6 -5
  5. data/lib/airbrake-ruby/config.rb +30 -30
  6. data/lib/airbrake-ruby/config/processor.rb +69 -0
  7. data/lib/airbrake-ruby/config/validator.rb +6 -0
  8. data/lib/airbrake-ruby/file_cache.rb +1 -1
  9. data/lib/airbrake-ruby/filter_chain.rb +1 -0
  10. data/lib/airbrake-ruby/filters/dependency_filter.rb +1 -0
  11. data/lib/airbrake-ruby/filters/gem_root_filter.rb +1 -0
  12. data/lib/airbrake-ruby/filters/git_last_checkout_filter.rb +1 -2
  13. data/lib/airbrake-ruby/filters/git_repository_filter.rb +3 -0
  14. data/lib/airbrake-ruby/filters/git_revision_filter.rb +2 -0
  15. data/lib/airbrake-ruby/filters/keys_filter.rb +21 -13
  16. data/lib/airbrake-ruby/filters/root_directory_filter.rb +1 -0
  17. data/lib/airbrake-ruby/filters/sql_filter.rb +4 -4
  18. data/lib/airbrake-ruby/filters/system_exit_filter.rb +1 -0
  19. data/lib/airbrake-ruby/filters/thread_filter.rb +2 -0
  20. data/lib/airbrake-ruby/ignorable.rb +1 -0
  21. data/lib/airbrake-ruby/notice.rb +1 -8
  22. data/lib/airbrake-ruby/notice_notifier.rb +1 -0
  23. data/lib/airbrake-ruby/performance_breakdown.rb +1 -6
  24. data/lib/airbrake-ruby/performance_notifier.rb +2 -15
  25. data/lib/airbrake-ruby/promise.rb +1 -0
  26. data/lib/airbrake-ruby/query.rb +1 -6
  27. data/lib/airbrake-ruby/queue.rb +1 -8
  28. data/lib/airbrake-ruby/remote_settings.rb +145 -0
  29. data/lib/airbrake-ruby/remote_settings/callback.rb +44 -0
  30. data/lib/airbrake-ruby/remote_settings/settings_data.rb +116 -0
  31. data/lib/airbrake-ruby/request.rb +1 -8
  32. data/lib/airbrake-ruby/stat.rb +1 -12
  33. data/lib/airbrake-ruby/sync_sender.rb +3 -2
  34. data/lib/airbrake-ruby/tdigest.rb +2 -0
  35. data/lib/airbrake-ruby/thread_pool.rb +1 -0
  36. data/lib/airbrake-ruby/truncator.rb +8 -2
  37. data/lib/airbrake-ruby/version.rb +11 -1
  38. data/spec/airbrake_spec.rb +45 -22
  39. data/spec/backtrace_spec.rb +26 -26
  40. data/spec/code_hunk_spec.rb +2 -2
  41. data/spec/config/processor_spec.rb +125 -0
  42. data/spec/config/validator_spec.rb +18 -1
  43. data/spec/config_spec.rb +11 -32
  44. data/spec/filters/gem_root_filter_spec.rb +4 -4
  45. data/spec/filters/keys_allowlist_spec.rb +1 -0
  46. data/spec/filters/keys_blocklist_spec.rb +10 -0
  47. data/spec/filters/root_directory_filter_spec.rb +4 -4
  48. data/spec/filters/sql_filter_spec.rb +2 -2
  49. data/spec/notice_notifier/options_spec.rb +2 -2
  50. data/spec/notice_notifier_spec.rb +2 -2
  51. data/spec/notice_spec.rb +1 -1
  52. data/spec/performance_breakdown_spec.rb +0 -12
  53. data/spec/performance_notifier_spec.rb +0 -25
  54. data/spec/query_spec.rb +1 -11
  55. data/spec/queue_spec.rb +1 -13
  56. data/spec/remote_settings/callback_spec.rb +141 -0
  57. data/spec/remote_settings/settings_data_spec.rb +348 -0
  58. data/spec/remote_settings_spec.rb +230 -0
  59. data/spec/request_spec.rb +1 -13
  60. data/spec/spec_helper.rb +4 -4
  61. data/spec/stat_spec.rb +0 -9
  62. data/spec/sync_sender_spec.rb +3 -1
  63. metadata +18 -6
@@ -0,0 +1,44 @@
1
+ module Airbrake
2
+ class RemoteSettings
3
+ # Callback is a class that provides a callback for the config poller, which
4
+ # updates the local config according to the data.
5
+ #
6
+ # @api private
7
+ # @since 5.0.2
8
+ class Callback
9
+ def initialize(config)
10
+ @config = config
11
+ @orig_error_notifications = config.error_notifications
12
+ @orig_performance_stats = config.performance_stats
13
+ end
14
+
15
+ # @param [Airbrake::RemoteSettings::SettingsData] data
16
+ # @return [void]
17
+ def call(data)
18
+ @config.logger.debug(
19
+ "#{LOG_LABEL} applying remote settings: #{data.to_h}",
20
+ )
21
+
22
+ @config.error_host = data.error_host if data.error_host
23
+ @config.apm_host = data.apm_host if data.apm_host
24
+
25
+ process_error_notifications(data)
26
+ process_performance_stats(data)
27
+ end
28
+
29
+ private
30
+
31
+ def process_error_notifications(data)
32
+ return unless @orig_error_notifications
33
+
34
+ @config.error_notifications = data.error_notifications?
35
+ end
36
+
37
+ def process_performance_stats(data)
38
+ return unless @orig_performance_stats
39
+
40
+ @config.performance_stats = data.performance_stats?
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,116 @@
1
+ module Airbrake
2
+ class RemoteSettings
3
+ # SettingsData is a container, which wraps JSON payload returned by the
4
+ # remote settings API. It exposes the payload via convenient methods and
5
+ # also ensures that in case some data from the payload is missing, a default
6
+ # value would be returned instead.
7
+ #
8
+ # @example
9
+ # # Create the object and pass initial data (empty hash).
10
+ # settings_data = SettingsData.new({})
11
+ #
12
+ # settings_data.interval #=> 600
13
+ #
14
+ # @since 5.0.0
15
+ # @api private
16
+ class SettingsData
17
+ # @return [Integer] how frequently we should poll the config API
18
+ DEFAULT_INTERVAL = 600
19
+
20
+ # @return [String] API version of the S3 API to poll
21
+ API_VER = '2020-06-18'.freeze
22
+
23
+ # @return [String] what path to poll
24
+ CONFIG_ROUTE_PATTERN =
25
+ "%<host>s/#{API_VER}/config/%<project_id>s/config.json".freeze
26
+
27
+ # @return [Hash{Symbol=>String}] the hash of all supported settings where
28
+ # the value is the name of the setting returned by the API
29
+ SETTINGS = {
30
+ errors: 'errors'.freeze,
31
+ apm: 'apm'.freeze,
32
+ }.freeze
33
+
34
+ # @param [Integer] project_id
35
+ # @param [Hash{String=>Object}] data
36
+ def initialize(project_id, data)
37
+ @project_id = project_id
38
+ @data = data
39
+ end
40
+
41
+ # Merges the given +hash+ with internal data.
42
+ #
43
+ # @param [Hash{String=>Object}] hash
44
+ # @return [self]
45
+ def merge!(hash)
46
+ @data.merge!(hash)
47
+
48
+ self
49
+ end
50
+
51
+ # @return [Integer] how frequently we should poll for the config
52
+ def interval
53
+ return DEFAULT_INTERVAL if !@data.key?('poll_sec') || !@data['poll_sec']
54
+
55
+ @data['poll_sec'] > 0 ? @data['poll_sec'] : DEFAULT_INTERVAL
56
+ end
57
+
58
+ # @param [String] remote_config_host
59
+ # @return [String] where the config is stored on S3.
60
+ def config_route(remote_config_host)
61
+ if @data['config_route'] && !@data['config_route'].empty?
62
+ return remote_config_host.chomp('/') + '/' + @data['config_route']
63
+ end
64
+
65
+ format(
66
+ CONFIG_ROUTE_PATTERN,
67
+ host: remote_config_host.chomp('/'),
68
+ project_id: @project_id,
69
+ )
70
+ end
71
+
72
+ # @return [Boolean] whether error notifications are enabled
73
+ def error_notifications?
74
+ return true unless (s = find_setting(SETTINGS[:errors]))
75
+
76
+ s['enabled']
77
+ end
78
+
79
+ # @return [Boolean] whether APM is enabled
80
+ def performance_stats?
81
+ return true unless (s = find_setting(SETTINGS[:apm]))
82
+
83
+ s['enabled']
84
+ end
85
+
86
+ # @return [String, nil] the host, which provides the API endpoint to which
87
+ # exceptions should be sent
88
+ def error_host
89
+ return unless (s = find_setting(SETTINGS[:errors]))
90
+
91
+ s['endpoint']
92
+ end
93
+
94
+ # @return [String, nil] the host, which provides the API endpoint to which
95
+ # APM data should be sent
96
+ def apm_host
97
+ return unless (s = find_setting(SETTINGS[:apm]))
98
+
99
+ s['endpoint']
100
+ end
101
+
102
+ # @return [Hash{String=>Object}] raw representation of JSON payload
103
+ def to_h
104
+ @data.dup
105
+ end
106
+
107
+ private
108
+
109
+ def find_setting(name)
110
+ return unless @data.key?('settings')
111
+
112
+ @data['settings'].find { |s| s['name'] == name }
113
+ end
114
+ end
115
+ end
116
+ end
@@ -4,7 +4,6 @@ module Airbrake
4
4
  # @see Airbrake.notify_request
5
5
  # @api public
6
6
  # @since v3.2.0
7
- # rubocop:disable Metrics/ParameterLists
8
7
  class Request
9
8
  include HashKeyable
10
9
  include Ignorable
@@ -12,15 +11,12 @@ module Airbrake
12
11
  include Mergeable
13
12
  include Grouppable
14
13
 
15
- attr_accessor :method, :route, :status_code, :start_time, :end_time,
16
- :timing, :time
14
+ attr_accessor :method, :route, :status_code, :timing, :time
17
15
 
18
16
  def initialize(
19
17
  method:,
20
18
  route:,
21
19
  status_code:,
22
- start_time: Time.now,
23
- end_time: start_time + 1,
24
20
  timing: nil,
25
21
  time: Time.now
26
22
  )
@@ -28,8 +24,6 @@ module Airbrake
28
24
  @method = method
29
25
  @route = route
30
26
  @status_code = status_code
31
- @start_time = start_time
32
- @end_time = end_time
33
27
  @timing = timing
34
28
  @time = time
35
29
  end
@@ -51,5 +45,4 @@ module Airbrake
51
45
  }.delete_if { |_key, val| val.nil? }
52
46
  end
53
47
  end
54
- # rubocop:enable Metrics/ParameterLists
55
48
  end
@@ -9,7 +9,7 @@ module Airbrake
9
9
  #
10
10
  # @example
11
11
  # stat = Airbrake::Stat.new
12
- # stat.increment(Time.now - 200)
12
+ # stat.increment_ms(2000)
13
13
  # stat.to_h # Pack and serialize data so it can be transmitted.
14
14
  #
15
15
  # @since v3.2.0
@@ -41,17 +41,6 @@ module Airbrake
41
41
  end
42
42
  end
43
43
 
44
- # Increments tdigest timings and updates tdigest with the difference between
45
- # +end_time+ and +start_time+.
46
- #
47
- # @param [Date] start_time
48
- # @param [Date] end_time
49
- # @return [void]
50
- def increment(start_time, end_time = nil)
51
- end_time ||= Time.new
52
- increment_ms((end_time - start_time) * 1000)
53
- end
54
-
55
44
  # Increments tdigest timings and updates tdigest with given +ms+ value.
56
45
  #
57
46
  # @param [Float] ms
@@ -23,7 +23,7 @@ module Airbrake
23
23
  # @param [#to_json] data
24
24
  # @param [URI::HTTPS] endpoint
25
25
  # @return [Hash{String=>String}] the parsed HTTP response
26
- def send(data, promise, endpoint = @config.endpoint)
26
+ def send(data, promise, endpoint = @config.error_endpoint)
27
27
  return promise if rate_limited_ip?(promise)
28
28
 
29
29
  response = nil
@@ -47,6 +47,7 @@ module Airbrake
47
47
  end
48
48
 
49
49
  return promise.reject(parsed_resp['error']) if parsed_resp.key?('error')
50
+
50
51
  promise.resolve(parsed_resp)
51
52
  end
52
53
 
@@ -79,7 +80,7 @@ module Airbrake
79
80
  req['Authorization'] = "Bearer #{@config.project_key}"
80
81
  req['Content-Type'] = CONTENT_TYPE
81
82
  req['User-Agent'] =
82
- "#{Airbrake::Notice::NOTIFIER[:name]}/#{Airbrake::AIRBRAKE_RUBY_VERSION}" \
83
+ "#{Airbrake::NOTIFIER_INFO[:name]}/#{Airbrake::AIRBRAKE_RUBY_VERSION}" \
83
84
  " Ruby/#{RUBY_VERSION}"
84
85
 
85
86
  req
@@ -200,6 +200,7 @@ module Airbrake
200
200
  unless (0..1).cover?(item)
201
201
  raise ArgumentError, "p should be in [0,1], got #{item}"
202
202
  end
203
+
203
204
  if size == 0
204
205
  nil
205
206
  else
@@ -271,6 +272,7 @@ module Airbrake
271
272
  shift = 7
272
273
  while (v & 0x80) != 0
273
274
  raise 'Shift too large in decode' if shift > 28
275
+
274
276
  v = counts_bytes.shift || 0
275
277
  z += (v & 0x7f) << shift
276
278
  shift += 7
@@ -129,6 +129,7 @@ module Airbrake
129
129
  Thread.new do
130
130
  while (message = @queue.pop)
131
131
  break if message == :stop
132
+
132
133
  @block.call(message)
133
134
  end
134
135
  end
@@ -12,6 +12,10 @@ module Airbrake
12
12
  # strings with +ENCODING_OPTIONS+
13
13
  TEMP_ENCODING = 'utf-16'.freeze
14
14
 
15
+ # @return [Array<Encoding>] encodings that are eligible for fixing invalid
16
+ # characters
17
+ SUPPORTED_ENCODINGS = [Encoding::UTF_8, Encoding::ASCII].freeze
18
+
15
19
  # @return [String] what to append when something is a circular reference
16
20
  CIRCULAR = '[Circular]'.freeze
17
21
 
@@ -35,6 +39,7 @@ module Airbrake
35
39
  def truncate(object, seen = Set.new)
36
40
  if seen.include?(object.object_id)
37
41
  return CIRCULAR if CIRCULAR_TYPES.any? { |t| object.is_a?(t) }
42
+
38
43
  return object
39
44
  end
40
45
  truncate_object(object, seen << object.object_id)
@@ -63,6 +68,7 @@ module Airbrake
63
68
  def truncate_string(str)
64
69
  fixed_str = replace_invalid_characters(str)
65
70
  return fixed_str if fixed_str.length <= @max_size
71
+
66
72
  (fixed_str.slice(0, @max_size) + TRUNCATED).freeze
67
73
  end
68
74
 
@@ -76,6 +82,7 @@ module Airbrake
76
82
  truncated_hash = {}
77
83
  hash.each_with_index do |(key, val), idx|
78
84
  break if idx + 1 > @max_size
85
+
79
86
  truncated_hash[key] = truncate(val, seen)
80
87
  end
81
88
 
@@ -103,8 +110,7 @@ module Airbrake
103
110
  # @return [String] a UTF-8 encoded string
104
111
  # @see https://github.com/flori/json/commit/3e158410e81f94dbbc3da6b7b35f4f64983aa4e3
105
112
  def replace_invalid_characters(str)
106
- encoding = str.encoding
107
- utf8_string = (encoding == Encoding::UTF_8 || encoding == Encoding::ASCII)
113
+ utf8_string = SUPPORTED_ENCODINGS.include?(str.encoding)
108
114
  return str if utf8_string && str.valid_encoding?
109
115
 
110
116
  temp_str = str.dup
@@ -2,5 +2,15 @@
2
2
  # More information: http://semver.org/
3
3
  module Airbrake
4
4
  # @return [String] the library version
5
- AIRBRAKE_RUBY_VERSION = '4.15.0'.freeze
5
+ # @api public
6
+ AIRBRAKE_RUBY_VERSION = '5.0.2'.freeze
7
+
8
+ # @return [Hash{Symbol=>String}] the information about the notifier library
9
+ # @since 5.0.0
10
+ # @api public
11
+ NOTIFIER_INFO = {
12
+ name: 'airbrake-ruby'.freeze,
13
+ version: Airbrake::AIRBRAKE_RUBY_VERSION,
14
+ url: 'https://github.com/airbrake/airbrake-ruby'.freeze,
15
+ }.freeze
6
16
  end
@@ -1,4 +1,13 @@
1
1
  RSpec.describe Airbrake do
2
+ let(:remote_settings) { instance_double(Airbrake::RemoteSettings) }
3
+
4
+ before do
5
+ allow(Airbrake::RemoteSettings).to receive(:poll).and_return(remote_settings)
6
+ allow(remote_settings).to receive(:stop_polling)
7
+ end
8
+
9
+ after { described_class.instance_variable_set(:@remote_settings, nil) }
10
+
2
11
  it "gets initialized with a performance notifier" do
3
12
  expect(described_class.performance_notifier).not_to be_nil
4
13
  end
@@ -100,10 +109,10 @@ RSpec.describe Airbrake do
100
109
  end
101
110
 
102
111
  context "when blocklist_keys gets configured" do
103
- before { allow(Airbrake.notice_notifier).to receive(:add_filter) }
112
+ before { allow(described_class.notice_notifier).to receive(:add_filter) }
104
113
 
105
114
  it "adds blocklist filter" do
106
- expect(Airbrake.notice_notifier).to receive(:add_filter)
115
+ expect(described_class.notice_notifier).to receive(:add_filter)
107
116
  .with(an_instance_of(Airbrake::Filters::KeysBlocklist))
108
117
  described_class.configure { |c| c.blocklist_keys = %w[password] }
109
118
  end
@@ -115,10 +124,10 @@ RSpec.describe Airbrake do
115
124
  end
116
125
 
117
126
  context "when allowlist_keys gets configured" do
118
- before { allow(Airbrake.notice_notifier).to receive(:add_filter) }
127
+ before { allow(described_class.notice_notifier).to receive(:add_filter) }
119
128
 
120
129
  it "adds allowlist filter" do
121
- expect(Airbrake.notice_notifier).to receive(:add_filter)
130
+ expect(described_class.notice_notifier).to receive(:add_filter)
122
131
  .with(an_instance_of(Airbrake::Filters::KeysAllowlist))
123
132
  described_class.configure { |c| c.allowlist_keys = %w[banana] }
124
133
  end
@@ -130,10 +139,10 @@ RSpec.describe Airbrake do
130
139
  end
131
140
 
132
141
  context "when root_directory gets configured" do
133
- before { allow(Airbrake.notice_notifier).to receive(:add_filter) }
142
+ before { allow(described_class.notice_notifier).to receive(:add_filter) }
134
143
 
135
144
  it "adds root directory filter" do
136
- expect(Airbrake.notice_notifier).to receive(:add_filter)
145
+ expect(described_class.notice_notifier).to receive(:add_filter)
137
146
  .with(an_instance_of(Airbrake::Filters::RootDirectoryFilter))
138
147
  described_class.configure { |c| c.root_directory = '/my/path' }
139
148
  end
@@ -145,7 +154,7 @@ RSpec.describe Airbrake do
145
154
  end
146
155
 
147
156
  it "adds git revision filter" do
148
- expect(Airbrake.notice_notifier).to receive(:add_filter)
157
+ expect(described_class.notice_notifier).to receive(:add_filter)
149
158
  .with(an_instance_of(Airbrake::Filters::GitRevisionFilter))
150
159
  described_class.configure { |c| c.root_directory = '/my/path' }
151
160
  end
@@ -157,7 +166,7 @@ RSpec.describe Airbrake do
157
166
  end
158
167
 
159
168
  it "adds git repository filter" do
160
- expect(Airbrake.notice_notifier).to receive(:add_filter)
169
+ expect(described_class.notice_notifier).to receive(:add_filter)
161
170
  .with(an_instance_of(Airbrake::Filters::GitRepositoryFilter))
162
171
  described_class.configure { |c| c.root_directory = '/my/path' }
163
172
  end
@@ -169,7 +178,7 @@ RSpec.describe Airbrake do
169
178
  end
170
179
 
171
180
  it "adds git last checkout filter" do
172
- expect(Airbrake.notice_notifier).to receive(:add_filter)
181
+ expect(described_class.notice_notifier).to receive(:add_filter)
173
182
  .with(an_instance_of(Airbrake::Filters::GitLastCheckoutFilter))
174
183
  described_class.configure { |c| c.root_directory = '/my/path' }
175
184
  end
@@ -182,7 +191,7 @@ RSpec.describe Airbrake do
182
191
  end
183
192
  end
184
193
 
185
- describe "#notify_request" do
194
+ describe ".notify_request" do
186
195
  context "when :stash key is not provided" do
187
196
  it "doesn't add anything to the stash of the request" do
188
197
  expect(described_class.performance_notifier).to receive(:notify) do |request|
@@ -217,7 +226,7 @@ RSpec.describe Airbrake do
217
226
  end
218
227
  end
219
228
 
220
- describe "#notify_request_sync" do
229
+ describe ".notify_request_sync" do
221
230
  it "notifies request synchronously" do
222
231
  expect(described_class.performance_notifier).to receive(:notify_sync)
223
232
 
@@ -233,7 +242,7 @@ RSpec.describe Airbrake do
233
242
  end
234
243
  end
235
244
 
236
- describe "#notify_query" do
245
+ describe ".notify_query" do
237
246
  context "when :stash key is not provided" do
238
247
  it "doesn't add anything to the stash of the query" do
239
248
  expect(described_class.performance_notifier).to receive(:notify) do |query|
@@ -268,7 +277,7 @@ RSpec.describe Airbrake do
268
277
  end
269
278
  end
270
279
 
271
- describe "#notify_query_sync" do
280
+ describe ".notify_query_sync" do
272
281
  it "notifies query synchronously" do
273
282
  expect(described_class.performance_notifier).to receive(:notify_sync)
274
283
 
@@ -284,7 +293,7 @@ RSpec.describe Airbrake do
284
293
  end
285
294
  end
286
295
 
287
- describe "#notify_performance_breakdown" do
296
+ describe ".notify_performance_breakdown" do
288
297
  context "when :stash key is not provided" do
289
298
  it "doesn't add anything to the stash of the performance breakdown" do
290
299
  expect(described_class.performance_notifier).to receive(:notify) do |query|
@@ -322,7 +331,7 @@ RSpec.describe Airbrake do
322
331
  end
323
332
  end
324
333
 
325
- describe "#notify_performance_breakdown_sync" do
334
+ describe ".notify_performance_breakdown_sync" do
326
335
  it "notifies performance breakdown synchronously" do
327
336
  expect(described_class.performance_notifier).to receive(:notify_sync)
328
337
 
@@ -339,7 +348,7 @@ RSpec.describe Airbrake do
339
348
  end
340
349
  end
341
350
 
342
- describe "#notify_queue" do
351
+ describe ".notify_queue" do
343
352
  context "when :stash key is not provided" do
344
353
  it "doesn't add anything to the stash of the queue" do
345
354
  expect(described_class.performance_notifier).to receive(:notify) do |queue|
@@ -370,7 +379,7 @@ RSpec.describe Airbrake do
370
379
  end
371
380
  end
372
381
 
373
- describe "#notify_queue_sync" do
382
+ describe ".notify_queue_sync" do
374
383
  it "notifies queue synchronously" do
375
384
  expect(described_class.performance_notifier).to receive(:notify_sync)
376
385
 
@@ -404,33 +413,47 @@ RSpec.describe Airbrake do
404
413
  end
405
414
 
406
415
  describe ".close" do
407
- after { Airbrake.reset }
416
+ after { described_class.reset }
408
417
 
409
418
  context "when notice_notifier is defined" do
410
419
  it "gets closed" do
411
- expect(Airbrake.notice_notifier).to receive(:close)
420
+ expect(described_class.notice_notifier).to receive(:close)
412
421
  end
413
422
  end
414
423
 
415
424
  context "when notice_notifier is undefined" do
416
425
  it "doesn't get closed (because it wasn't initialized)" do
417
- Airbrake.instance_variable_set(:@notice_notifier, nil)
426
+ described_class.instance_variable_set(:@notice_notifier, nil)
418
427
  expect_any_instance_of(Airbrake::NoticeNotifier).not_to receive(:close)
419
428
  end
420
429
  end
421
430
 
422
431
  context "when performance_notifier is defined" do
423
432
  it "gets closed" do
424
- expect(Airbrake.performance_notifier).to receive(:close)
433
+ expect(described_class.performance_notifier).to receive(:close)
425
434
  end
426
435
  end
427
436
 
428
437
  context "when perforance_notifier is undefined" do
429
438
  it "doesn't get closed (because it wasn't initialized)" do
430
- Airbrake.instance_variable_set(:@performance_notifier, nil)
439
+ described_class.instance_variable_set(:@performance_notifier, nil)
431
440
  expect_any_instance_of(Airbrake::PerformanceNotifier)
432
441
  .not_to receive(:close)
433
442
  end
434
443
  end
444
+
445
+ context "when remote settings are defined" do
446
+ it "stops polling" do
447
+ described_class.instance_variable_set(:@remote_settings, remote_settings)
448
+ expect(remote_settings).to receive(:stop_polling)
449
+ end
450
+ end
451
+
452
+ context "when remote settings are undefined" do
453
+ it "doesn't stop polling (because they weren't initialized)" do
454
+ described_class.instance_variable_set(:@remote_settings, nil)
455
+ expect(remote_settings).not_to receive(:stop_polling)
456
+ end
457
+ end
435
458
  end
436
459
  end