airbrake-ruby 4.8.0 → 5.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (91) hide show
  1. checksums.yaml +4 -4
  2. data/lib/airbrake-ruby.rb +132 -57
  3. data/lib/airbrake-ruby/async_sender.rb +7 -30
  4. data/lib/airbrake-ruby/backtrace.rb +8 -7
  5. data/lib/airbrake-ruby/benchmark.rb +1 -1
  6. data/lib/airbrake-ruby/code_hunk.rb +1 -1
  7. data/lib/airbrake-ruby/config.rb +59 -15
  8. data/lib/airbrake-ruby/config/processor.rb +71 -0
  9. data/lib/airbrake-ruby/config/validator.rb +9 -3
  10. data/lib/airbrake-ruby/deploy_notifier.rb +1 -1
  11. data/lib/airbrake-ruby/file_cache.rb +1 -1
  12. data/lib/airbrake-ruby/filter_chain.rb +16 -1
  13. data/lib/airbrake-ruby/filters/dependency_filter.rb +1 -0
  14. data/lib/airbrake-ruby/filters/exception_attributes_filter.rb +2 -2
  15. data/lib/airbrake-ruby/filters/gem_root_filter.rb +1 -0
  16. data/lib/airbrake-ruby/filters/git_last_checkout_filter.rb +5 -5
  17. data/lib/airbrake-ruby/filters/git_repository_filter.rb +3 -0
  18. data/lib/airbrake-ruby/filters/git_revision_filter.rb +2 -0
  19. data/lib/airbrake-ruby/filters/{keys_whitelist.rb → keys_allowlist.rb} +3 -3
  20. data/lib/airbrake-ruby/filters/{keys_blacklist.rb → keys_blocklist.rb} +3 -3
  21. data/lib/airbrake-ruby/filters/keys_filter.rb +39 -20
  22. data/lib/airbrake-ruby/filters/root_directory_filter.rb +1 -0
  23. data/lib/airbrake-ruby/filters/sql_filter.rb +7 -7
  24. data/lib/airbrake-ruby/filters/system_exit_filter.rb +1 -0
  25. data/lib/airbrake-ruby/filters/thread_filter.rb +5 -4
  26. data/lib/airbrake-ruby/grouppable.rb +12 -0
  27. data/lib/airbrake-ruby/ignorable.rb +1 -0
  28. data/lib/airbrake-ruby/inspectable.rb +2 -2
  29. data/lib/airbrake-ruby/loggable.rb +1 -1
  30. data/lib/airbrake-ruby/mergeable.rb +12 -0
  31. data/lib/airbrake-ruby/monotonic_time.rb +5 -0
  32. data/lib/airbrake-ruby/notice.rb +7 -14
  33. data/lib/airbrake-ruby/notice_notifier.rb +11 -3
  34. data/lib/airbrake-ruby/performance_breakdown.rb +16 -10
  35. data/lib/airbrake-ruby/performance_notifier.rb +80 -58
  36. data/lib/airbrake-ruby/promise.rb +1 -0
  37. data/lib/airbrake-ruby/query.rb +20 -15
  38. data/lib/airbrake-ruby/queue.rb +65 -0
  39. data/lib/airbrake-ruby/remote_settings.rb +105 -0
  40. data/lib/airbrake-ruby/remote_settings/callback.rb +44 -0
  41. data/lib/airbrake-ruby/remote_settings/settings_data.rb +116 -0
  42. data/lib/airbrake-ruby/request.rb +14 -12
  43. data/lib/airbrake-ruby/stat.rb +26 -33
  44. data/lib/airbrake-ruby/sync_sender.rb +3 -2
  45. data/lib/airbrake-ruby/tdigest.rb +43 -58
  46. data/lib/airbrake-ruby/thread_pool.rb +11 -1
  47. data/lib/airbrake-ruby/truncator.rb +10 -4
  48. data/lib/airbrake-ruby/version.rb +11 -1
  49. data/spec/airbrake_spec.rb +206 -71
  50. data/spec/async_sender_spec.rb +3 -12
  51. data/spec/backtrace_spec.rb +44 -44
  52. data/spec/code_hunk_spec.rb +11 -11
  53. data/spec/config/processor_spec.rb +143 -0
  54. data/spec/config/validator_spec.rb +23 -6
  55. data/spec/config_spec.rb +40 -14
  56. data/spec/deploy_notifier_spec.rb +2 -2
  57. data/spec/filter_chain_spec.rb +28 -1
  58. data/spec/filters/dependency_filter_spec.rb +1 -1
  59. data/spec/filters/gem_root_filter_spec.rb +9 -9
  60. data/spec/filters/git_last_checkout_filter_spec.rb +21 -4
  61. data/spec/filters/git_repository_filter.rb +1 -1
  62. data/spec/filters/git_revision_filter_spec.rb +10 -10
  63. data/spec/filters/{keys_whitelist_spec.rb → keys_allowlist_spec.rb} +29 -28
  64. data/spec/filters/{keys_blacklist_spec.rb → keys_blocklist_spec.rb} +39 -29
  65. data/spec/filters/root_directory_filter_spec.rb +9 -9
  66. data/spec/filters/sql_filter_spec.rb +58 -60
  67. data/spec/filters/system_exit_filter_spec.rb +1 -1
  68. data/spec/filters/thread_filter_spec.rb +32 -30
  69. data/spec/fixtures/project_root/code.rb +9 -9
  70. data/spec/loggable_spec.rb +17 -0
  71. data/spec/monotonic_time_spec.rb +11 -0
  72. data/spec/notice_notifier/options_spec.rb +17 -17
  73. data/spec/notice_notifier_spec.rb +20 -20
  74. data/spec/notice_spec.rb +6 -6
  75. data/spec/performance_breakdown_spec.rb +0 -1
  76. data/spec/performance_notifier_spec.rb +220 -73
  77. data/spec/query_spec.rb +1 -1
  78. data/spec/queue_spec.rb +18 -0
  79. data/spec/remote_settings/callback_spec.rb +143 -0
  80. data/spec/remote_settings/settings_data_spec.rb +348 -0
  81. data/spec/remote_settings_spec.rb +187 -0
  82. data/spec/request_spec.rb +1 -3
  83. data/spec/response_spec.rb +8 -8
  84. data/spec/spec_helper.rb +6 -6
  85. data/spec/stat_spec.rb +2 -12
  86. data/spec/sync_sender_spec.rb +14 -12
  87. data/spec/tdigest_spec.rb +7 -7
  88. data/spec/thread_pool_spec.rb +39 -10
  89. data/spec/timed_trace_spec.rb +1 -1
  90. data/spec/truncator_spec.rb +12 -12
  91. metadata +32 -14
@@ -45,7 +45,15 @@ module Airbrake
45
45
  # @return [Boolean] true if the message was successfully sent to the pool,
46
46
  # false if the queue is full
47
47
  def <<(message)
48
- return false if backlog >= @queue_size
48
+ if backlog >= @queue_size
49
+ logger.error(
50
+ "#{LOG_LABEL} ThreadPool has reached its capacity of " \
51
+ "#{@queue_size} and the following message will not be " \
52
+ "processed: #{message.inspect}",
53
+ )
54
+ return false
55
+ end
56
+
49
57
  @queue << message
50
58
  true
51
59
  end
@@ -75,6 +83,7 @@ module Airbrake
75
83
 
76
84
  if @pid != Process.pid && @workers.list.empty?
77
85
  @pid = Process.pid
86
+ @workers = ThreadGroup.new
78
87
  spawn_workers
79
88
  end
80
89
 
@@ -120,6 +129,7 @@ module Airbrake
120
129
  Thread.new do
121
130
  while (message = @queue.pop)
122
131
  break if message == :stop
132
+
123
133
  @block.call(message)
124
134
  end
125
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,13 +110,12 @@ 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
111
- temp_str.encode!(TEMP_ENCODING, ENCODING_OPTIONS) if utf8_string
112
- temp_str.encode!('utf-8', ENCODING_OPTIONS)
117
+ temp_str.encode!(TEMP_ENCODING, **ENCODING_OPTIONS) if utf8_string
118
+ temp_str.encode!('utf-8', **ENCODING_OPTIONS)
113
119
  end
114
120
  end
115
121
  end
@@ -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.8.0'.freeze
5
+ # @api public
6
+ AIRBRAKE_RUBY_VERSION = '5.2.0'.freeze
7
+
8
+ # @return [Hash{Symbol=>String}] the information about the notifier library
9
+ # @since v5.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
@@ -51,28 +60,6 @@ RSpec.describe Airbrake do
51
60
  expect(described_class).to be_configured
52
61
  end
53
62
 
54
- context "when a notifier was configured" do
55
- before do
56
- expect(described_class).to receive(:configured?).and_return(true)
57
- end
58
-
59
- it "closes previously configured notice notifier" do
60
- expect(described_class).to receive(:close)
61
- described_class.configure {}
62
- end
63
- end
64
-
65
- context "when a notifier wasn't configured" do
66
- before do
67
- expect(described_class).to receive(:configured?).and_return(false)
68
- end
69
-
70
- it "doesn't close previously configured notice notifier" do
71
- expect(described_class).not_to receive(:close)
72
- described_class.configure {}
73
- end
74
- end
75
-
76
63
  context "when called multiple times" do
77
64
  it "doesn't overwrite performance notifier" do
78
65
  described_class.configure {}
@@ -97,43 +84,65 @@ RSpec.describe Airbrake do
97
84
  described_class.configure {}
98
85
  expect(described_class.deploy_notifier).to eql(deploy_notifier)
99
86
  end
87
+
88
+ it "doesn't append the same notice notifier filters over and over" do
89
+ described_class.configure do |c|
90
+ c.project_id = 1
91
+ c.project_key = '2'
92
+ end
93
+
94
+ expect(described_class.notice_notifier).not_to receive(:add_filter)
95
+ 10.times { described_class.configure {} }
96
+ end
97
+
98
+ it "appends some default filters" do
99
+ allow(described_class.notice_notifier).to receive(:add_filter)
100
+ expect(described_class.notice_notifier).to receive(:add_filter).with(
101
+ an_instance_of(Airbrake::Filters::RootDirectoryFilter),
102
+ )
103
+
104
+ described_class.configure do |c|
105
+ c.project_id = 1
106
+ c.project_key = '2'
107
+ end
108
+ end
100
109
  end
101
110
 
102
- context "when blacklist_keys gets configured" do
103
- before { allow(Airbrake.notice_notifier).to receive(:add_filter) }
111
+ context "when blocklist_keys gets configured" do
112
+ before { allow(described_class.notice_notifier).to receive(:add_filter) }
104
113
 
105
- it "adds blacklist filter" do
106
- expect(Airbrake.notice_notifier).to receive(:add_filter)
107
- .with(an_instance_of(Airbrake::Filters::KeysBlacklist))
108
- described_class.configure { |c| c.blacklist_keys = %w[password] }
114
+ it "adds blocklist filter" do
115
+ expect(described_class.notice_notifier).to receive(:add_filter)
116
+ .with(an_instance_of(Airbrake::Filters::KeysBlocklist))
117
+ described_class.configure { |c| c.blocklist_keys = %w[password] }
109
118
  end
110
119
 
111
- it "initializes blacklist with specified parameters" do
112
- expect(Airbrake::Filters::KeysBlacklist).to receive(:new).with(%w[password])
113
- described_class.configure { |c| c.blacklist_keys = %w[password] }
120
+ it "initializes blocklist with specified parameters" do
121
+ expect(Airbrake::Filters::KeysBlocklist).to receive(:new).with(%w[password])
122
+ described_class.configure { |c| c.blocklist_keys = %w[password] }
114
123
  end
115
124
  end
116
125
 
117
- context "when whitelist_keys gets configured" do
118
- before { allow(Airbrake.notice_notifier).to receive(:add_filter) }
126
+ context "when allowlist_keys gets configured" do
127
+ before { allow(described_class.notice_notifier).to receive(:add_filter) }
119
128
 
120
- it "adds whitelist filter" do
121
- expect(Airbrake.notice_notifier).to receive(:add_filter)
122
- .with(an_instance_of(Airbrake::Filters::KeysWhitelist))
123
- described_class.configure { |c| c.whitelist_keys = %w[banana] }
129
+ it "adds allowlist filter" do
130
+ expect(described_class.notice_notifier).to receive(:add_filter)
131
+ .with(an_instance_of(Airbrake::Filters::KeysAllowlist))
132
+ described_class.configure { |c| c.allowlist_keys = %w[banana] }
124
133
  end
125
134
 
126
- it "initializes whitelist with specified parameters" do
127
- expect(Airbrake::Filters::KeysWhitelist).to receive(:new).with(%w[banana])
128
- described_class.configure { |c| c.whitelist_keys = %w[banana] }
135
+ it "initializes allowlist with specified parameters" do
136
+ expect(Airbrake::Filters::KeysAllowlist).to receive(:new).with(%w[banana])
137
+ described_class.configure { |c| c.allowlist_keys = %w[banana] }
129
138
  end
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,20 +191,7 @@ RSpec.describe Airbrake do
182
191
  end
183
192
  end
184
193
 
185
- describe "#reset" do
186
- context "when Airbrake was previously configured" do
187
- before do
188
- expect(described_class).to receive(:configured?).and_return(true)
189
- end
190
-
191
- it "closes notice notifier" do
192
- expect(described_class).to receive(:close)
193
- subject.reset
194
- end
195
- end
196
- end
197
-
198
- describe "#notify_request" do
194
+ describe ".notify_request" do
199
195
  context "when :stash key is not provided" do
200
196
  it "doesn't add anything to the stash of the request" do
201
197
  expect(described_class.performance_notifier).to receive(:notify) do |request|
@@ -206,7 +202,7 @@ RSpec.describe Airbrake do
206
202
  method: 'GET',
207
203
  route: '/',
208
204
  status_code: 200,
209
- start_time: Time.now
205
+ timing: 1,
210
206
  )
211
207
  end
212
208
  end
@@ -222,15 +218,31 @@ RSpec.describe Airbrake do
222
218
  method: 'GET',
223
219
  route: '/',
224
220
  status_code: 200,
225
- start_time: Time.now
221
+ timing: 1,
226
222
  },
227
- request_id: 1
223
+ request_id: 1,
228
224
  )
229
225
  end
230
226
  end
231
227
  end
232
228
 
233
- describe "#notify_query" do
229
+ describe ".notify_request_sync" do
230
+ it "notifies request synchronously" do
231
+ expect(described_class.performance_notifier).to receive(:notify_sync)
232
+
233
+ described_class.notify_request_sync(
234
+ {
235
+ method: 'GET',
236
+ route: '/',
237
+ status_code: 200,
238
+ timing: 1,
239
+ },
240
+ request_id: 1,
241
+ )
242
+ end
243
+ end
244
+
245
+ describe ".notify_query" do
234
246
  context "when :stash key is not provided" do
235
247
  it "doesn't add anything to the stash of the query" do
236
248
  expect(described_class.performance_notifier).to receive(:notify) do |query|
@@ -241,7 +253,7 @@ RSpec.describe Airbrake do
241
253
  method: 'GET',
242
254
  route: '/',
243
255
  query: '',
244
- start_time: Time.now
256
+ timing: 1,
245
257
  )
246
258
  end
247
259
  end
@@ -257,15 +269,31 @@ RSpec.describe Airbrake do
257
269
  method: 'GET',
258
270
  route: '/',
259
271
  query: '',
260
- start_time: Time.now
272
+ timing: 1,
261
273
  },
262
- request_id: 1
274
+ request_id: 1,
263
275
  )
264
276
  end
265
277
  end
266
278
  end
267
279
 
268
- describe "#notify_performance_breakdown" do
280
+ describe ".notify_query_sync" do
281
+ it "notifies query synchronously" do
282
+ expect(described_class.performance_notifier).to receive(:notify_sync)
283
+
284
+ described_class.notify_query_sync(
285
+ {
286
+ method: 'GET',
287
+ route: '/',
288
+ query: '',
289
+ timing: 1,
290
+ },
291
+ request_id: 1,
292
+ )
293
+ end
294
+ end
295
+
296
+ describe ".notify_performance_breakdown" do
269
297
  context "when :stash key is not provided" do
270
298
  it "doesn't add anything to the stash of the performance breakdown" do
271
299
  expect(described_class.performance_notifier).to receive(:notify) do |query|
@@ -276,7 +304,7 @@ RSpec.describe Airbrake do
276
304
  method: 'GET',
277
305
  route: '/',
278
306
  query: '',
279
- start_time: Time.now
307
+ timing: 1,
280
308
  )
281
309
  end
282
310
  end
@@ -284,7 +312,7 @@ RSpec.describe Airbrake do
284
312
  context "when :stash key is provided" do
285
313
  it "adds the value as the stash of the performance breakdown" do
286
314
  expect(
287
- described_class.performance_notifier
315
+ described_class.performance_notifier,
288
316
  ).to receive(:notify) do |performance_breakdown|
289
317
  expect(performance_breakdown.stash).to eq(request_id: 1)
290
318
  end
@@ -295,14 +323,76 @@ RSpec.describe Airbrake do
295
323
  route: '/',
296
324
  response_type: :html,
297
325
  groups: {},
298
- start_time: Time.now
326
+ timing: 1,
327
+ },
328
+ request_id: 1,
329
+ )
330
+ end
331
+ end
332
+ end
333
+
334
+ describe ".notify_performance_breakdown_sync" do
335
+ it "notifies performance breakdown synchronously" do
336
+ expect(described_class.performance_notifier).to receive(:notify_sync)
337
+
338
+ described_class.notify_performance_breakdown_sync(
339
+ {
340
+ method: 'GET',
341
+ route: '/',
342
+ response_type: :html,
343
+ groups: {},
344
+ timing: 1,
345
+ },
346
+ request_id: 1,
347
+ )
348
+ end
349
+ end
350
+
351
+ describe ".notify_queue" do
352
+ context "when :stash key is not provided" do
353
+ it "doesn't add anything to the stash of the queue" do
354
+ expect(described_class.performance_notifier).to receive(:notify) do |queue|
355
+ expect(queue.stash).to be_empty
356
+ end
357
+
358
+ described_class.notify_queue(
359
+ queue: 'bananas',
360
+ error_count: 10,
361
+ )
362
+ end
363
+ end
364
+
365
+ context "when :stash key is provided" do
366
+ it "adds the value as the stash of the queue" do
367
+ expect(described_class.performance_notifier).to receive(:notify) do |queue|
368
+ expect(queue.stash).to eq(request_id: 1)
369
+ end
370
+
371
+ described_class.notify_queue(
372
+ {
373
+ queue: 'bananas',
374
+ error_count: 10,
299
375
  },
300
- request_id: 1
376
+ request_id: 1,
301
377
  )
302
378
  end
303
379
  end
304
380
  end
305
381
 
382
+ describe ".notify_queue_sync" do
383
+ it "notifies queue synchronously" do
384
+ expect(described_class.performance_notifier).to receive(:notify_sync)
385
+
386
+ described_class.notify_queue_sync(
387
+ {
388
+ queue: 'bananas',
389
+ error_count: 10,
390
+ },
391
+ request_id: 1,
392
+ )
393
+ end
394
+ end
395
+
306
396
  describe ".performance_notifier" do
307
397
  it "returns a performance notifier" do
308
398
  expect(described_class.performance_notifier)
@@ -321,4 +411,49 @@ RSpec.describe Airbrake do
321
411
  expect(described_class.deploy_notifier).to be_an(Airbrake::DeployNotifier)
322
412
  end
323
413
  end
414
+
415
+ describe ".close" do
416
+ after { described_class.reset }
417
+
418
+ context "when notice_notifier is defined" do
419
+ it "gets closed" do
420
+ expect(described_class.notice_notifier).to receive(:close)
421
+ end
422
+ end
423
+
424
+ context "when notice_notifier is undefined" do
425
+ it "doesn't get closed (because it wasn't initialized)" do
426
+ described_class.instance_variable_set(:@notice_notifier, nil)
427
+ expect_any_instance_of(Airbrake::NoticeNotifier).not_to receive(:close)
428
+ end
429
+ end
430
+
431
+ context "when performance_notifier is defined" do
432
+ it "gets closed" do
433
+ expect(described_class.performance_notifier).to receive(:close)
434
+ end
435
+ end
436
+
437
+ context "when perforance_notifier is undefined" do
438
+ it "doesn't get closed (because it wasn't initialized)" do
439
+ described_class.instance_variable_set(:@performance_notifier, nil)
440
+ expect_any_instance_of(Airbrake::PerformanceNotifier)
441
+ .not_to receive(:close)
442
+ end
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
458
+ end
324
459
  end