airbrake-ruby 4.8.0 → 5.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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