airbrake-ruby 4.1.0 → 5.0.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 (94) hide show
  1. checksums.yaml +5 -5
  2. data/lib/airbrake-ruby/async_sender.rb +22 -96
  3. data/lib/airbrake-ruby/backtrace.rb +8 -7
  4. data/lib/airbrake-ruby/benchmark.rb +39 -0
  5. data/lib/airbrake-ruby/code_hunk.rb +1 -1
  6. data/lib/airbrake-ruby/config/processor.rb +84 -0
  7. data/lib/airbrake-ruby/config/validator.rb +9 -3
  8. data/lib/airbrake-ruby/config.rb +76 -20
  9. data/lib/airbrake-ruby/deploy_notifier.rb +1 -1
  10. data/lib/airbrake-ruby/file_cache.rb +6 -0
  11. data/lib/airbrake-ruby/filter_chain.rb +16 -1
  12. data/lib/airbrake-ruby/filters/dependency_filter.rb +1 -0
  13. data/lib/airbrake-ruby/filters/exception_attributes_filter.rb +2 -2
  14. data/lib/airbrake-ruby/filters/gem_root_filter.rb +1 -0
  15. data/lib/airbrake-ruby/filters/git_last_checkout_filter.rb +5 -5
  16. data/lib/airbrake-ruby/filters/git_repository_filter.rb +3 -0
  17. data/lib/airbrake-ruby/filters/git_revision_filter.rb +2 -0
  18. data/lib/airbrake-ruby/filters/{keys_whitelist.rb → keys_allowlist.rb} +3 -3
  19. data/lib/airbrake-ruby/filters/{keys_blacklist.rb → keys_blocklist.rb} +3 -3
  20. data/lib/airbrake-ruby/filters/keys_filter.rb +39 -20
  21. data/lib/airbrake-ruby/filters/root_directory_filter.rb +1 -0
  22. data/lib/airbrake-ruby/filters/sql_filter.rb +30 -6
  23. data/lib/airbrake-ruby/filters/system_exit_filter.rb +1 -0
  24. data/lib/airbrake-ruby/filters/thread_filter.rb +4 -2
  25. data/lib/airbrake-ruby/grouppable.rb +12 -0
  26. data/lib/airbrake-ruby/ignorable.rb +1 -0
  27. data/lib/airbrake-ruby/inspectable.rb +2 -2
  28. data/lib/airbrake-ruby/loggable.rb +2 -2
  29. data/lib/airbrake-ruby/mergeable.rb +12 -0
  30. data/lib/airbrake-ruby/monotonic_time.rb +48 -0
  31. data/lib/airbrake-ruby/notice.rb +10 -20
  32. data/lib/airbrake-ruby/notice_notifier.rb +23 -42
  33. data/lib/airbrake-ruby/performance_breakdown.rb +52 -0
  34. data/lib/airbrake-ruby/performance_notifier.rb +126 -49
  35. data/lib/airbrake-ruby/promise.rb +1 -0
  36. data/lib/airbrake-ruby/query.rb +26 -11
  37. data/lib/airbrake-ruby/queue.rb +65 -0
  38. data/lib/airbrake-ruby/remote_settings/settings_data.rb +120 -0
  39. data/lib/airbrake-ruby/remote_settings.rb +145 -0
  40. data/lib/airbrake-ruby/request.rb +20 -6
  41. data/lib/airbrake-ruby/stashable.rb +15 -0
  42. data/lib/airbrake-ruby/stat.rb +34 -24
  43. data/lib/airbrake-ruby/sync_sender.rb +3 -2
  44. data/lib/airbrake-ruby/tdigest.rb +43 -58
  45. data/lib/airbrake-ruby/thread_pool.rb +138 -0
  46. data/lib/airbrake-ruby/timed_trace.rb +58 -0
  47. data/lib/airbrake-ruby/truncator.rb +10 -4
  48. data/lib/airbrake-ruby/version.rb +11 -1
  49. data/lib/airbrake-ruby.rb +219 -53
  50. data/spec/airbrake_spec.rb +428 -9
  51. data/spec/async_sender_spec.rb +26 -110
  52. data/spec/backtrace_spec.rb +44 -44
  53. data/spec/benchmark_spec.rb +33 -0
  54. data/spec/code_hunk_spec.rb +11 -11
  55. data/spec/config/processor_spec.rb +209 -0
  56. data/spec/config/validator_spec.rb +23 -6
  57. data/spec/config_spec.rb +77 -7
  58. data/spec/deploy_notifier_spec.rb +2 -2
  59. data/spec/{file_cache.rb → file_cache_spec.rb} +2 -4
  60. data/spec/filter_chain_spec.rb +28 -1
  61. data/spec/filters/dependency_filter_spec.rb +1 -1
  62. data/spec/filters/gem_root_filter_spec.rb +9 -9
  63. data/spec/filters/git_last_checkout_filter_spec.rb +21 -4
  64. data/spec/filters/git_repository_filter.rb +1 -1
  65. data/spec/filters/git_revision_filter_spec.rb +13 -11
  66. data/spec/filters/{keys_whitelist_spec.rb → keys_allowlist_spec.rb} +29 -28
  67. data/spec/filters/{keys_blacklist_spec.rb → keys_blocklist_spec.rb} +39 -29
  68. data/spec/filters/root_directory_filter_spec.rb +9 -9
  69. data/spec/filters/sql_filter_spec.rb +110 -55
  70. data/spec/filters/system_exit_filter_spec.rb +1 -1
  71. data/spec/filters/thread_filter_spec.rb +33 -31
  72. data/spec/fixtures/project_root/code.rb +9 -9
  73. data/spec/loggable_spec.rb +17 -0
  74. data/spec/monotonic_time_spec.rb +23 -0
  75. data/spec/{notice_notifier_spec → notice_notifier}/options_spec.rb +19 -21
  76. data/spec/notice_notifier_spec.rb +20 -80
  77. data/spec/notice_spec.rb +9 -11
  78. data/spec/performance_breakdown_spec.rb +11 -0
  79. data/spec/performance_notifier_spec.rb +360 -85
  80. data/spec/query_spec.rb +11 -0
  81. data/spec/queue_spec.rb +18 -0
  82. data/spec/remote_settings/settings_data_spec.rb +365 -0
  83. data/spec/remote_settings_spec.rb +230 -0
  84. data/spec/request_spec.rb +9 -0
  85. data/spec/response_spec.rb +8 -8
  86. data/spec/spec_helper.rb +9 -13
  87. data/spec/stashable_spec.rb +23 -0
  88. data/spec/stat_spec.rb +17 -15
  89. data/spec/sync_sender_spec.rb +14 -12
  90. data/spec/tdigest_spec.rb +6 -6
  91. data/spec/thread_pool_spec.rb +187 -0
  92. data/spec/timed_trace_spec.rb +125 -0
  93. data/spec/truncator_spec.rb +12 -12
  94. metadata +55 -18
@@ -1,7 +1,33 @@
1
1
  RSpec.describe Airbrake do
2
- before { Airbrake::Config.instance = Airbrake::Config.new }
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
+
11
+ it "gets initialized with a performance notifier" do
12
+ expect(described_class.performance_notifier).not_to be_nil
13
+ end
14
+
15
+ it "gets initialized with a notice notifier" do
16
+ expect(described_class.notice_notifier).not_to be_nil
17
+ end
18
+
19
+ it "gets initialized with a deploy notifier" do
20
+ expect(described_class.deploy_notifier).not_to be_nil
21
+ end
3
22
 
4
23
  describe ".configure" do
24
+ before do
25
+ Airbrake::Config.instance = Airbrake::Config.new
26
+ described_class.reset
27
+ end
28
+
29
+ after { described_class.reset }
30
+
5
31
  it "yields the config" do
6
32
  expect do |b|
7
33
  begin
@@ -23,17 +49,410 @@ RSpec.describe Airbrake do
23
49
  expect(Airbrake::Loggable.instance).to eql(logger)
24
50
  end
25
51
 
26
- context "when user config doesn't contain a project id" do
27
- it "raises error" do
28
- expect { described_class.configure { |c| c.project_key = '1' } }
29
- .to raise_error(Airbrake::Error, ':project_id is required')
52
+ it "makes Airbrake configured" do
53
+ expect(described_class).not_to be_configured
54
+
55
+ described_class.configure do |c|
56
+ c.project_id = 1
57
+ c.project_key = '2'
58
+ end
59
+
60
+ expect(described_class).to be_configured
61
+ end
62
+
63
+ context "when called multiple times" do
64
+ it "doesn't overwrite performance notifier" do
65
+ described_class.configure {}
66
+ performance_notifier = described_class.performance_notifier
67
+
68
+ described_class.configure {}
69
+ expect(described_class.performance_notifier).to eql(performance_notifier)
70
+ end
71
+
72
+ it "doesn't overwrite notice notifier" do
73
+ described_class.configure {}
74
+ notice_notifier = described_class.notice_notifier
75
+
76
+ described_class.configure {}
77
+ expect(described_class.notice_notifier).to eql(notice_notifier)
78
+ end
79
+
80
+ it "doesn't overwrite deploy notifier" do
81
+ described_class.configure {}
82
+ deploy_notifier = described_class.deploy_notifier
83
+
84
+ described_class.configure {}
85
+ expect(described_class.deploy_notifier).to eql(deploy_notifier)
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
109
+ end
110
+
111
+ context "when blocklist_keys gets configured" do
112
+ before { allow(described_class.notice_notifier).to receive(:add_filter) }
113
+
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] }
118
+ end
119
+
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] }
123
+ end
124
+ end
125
+
126
+ context "when allowlist_keys gets configured" do
127
+ before { allow(described_class.notice_notifier).to receive(:add_filter) }
128
+
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] }
133
+ end
134
+
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] }
138
+ end
139
+ end
140
+
141
+ context "when root_directory gets configured" do
142
+ before { allow(described_class.notice_notifier).to receive(:add_filter) }
143
+
144
+ it "adds root directory filter" do
145
+ expect(described_class.notice_notifier).to receive(:add_filter)
146
+ .with(an_instance_of(Airbrake::Filters::RootDirectoryFilter))
147
+ described_class.configure { |c| c.root_directory = '/my/path' }
148
+ end
149
+
150
+ it "initializes root directory filter with specified path" do
151
+ expect(Airbrake::Filters::RootDirectoryFilter)
152
+ .to receive(:new).with('/my/path')
153
+ described_class.configure { |c| c.root_directory = '/my/path' }
154
+ end
155
+
156
+ it "adds git revision filter" do
157
+ expect(described_class.notice_notifier).to receive(:add_filter)
158
+ .with(an_instance_of(Airbrake::Filters::GitRevisionFilter))
159
+ described_class.configure { |c| c.root_directory = '/my/path' }
160
+ end
161
+
162
+ it "initializes git revision filter with correct root directory" do
163
+ expect(Airbrake::Filters::GitRevisionFilter)
164
+ .to receive(:new).with('/my/path')
165
+ described_class.configure { |c| c.root_directory = '/my/path' }
166
+ end
167
+
168
+ it "adds git repository filter" do
169
+ expect(described_class.notice_notifier).to receive(:add_filter)
170
+ .with(an_instance_of(Airbrake::Filters::GitRepositoryFilter))
171
+ described_class.configure { |c| c.root_directory = '/my/path' }
172
+ end
173
+
174
+ it "initializes git repository filter with correct root directory" do
175
+ expect(Airbrake::Filters::GitRepositoryFilter)
176
+ .to receive(:new).with('/my/path')
177
+ described_class.configure { |c| c.root_directory = '/my/path' }
178
+ end
179
+
180
+ it "adds git last checkout filter" do
181
+ expect(described_class.notice_notifier).to receive(:add_filter)
182
+ .with(an_instance_of(Airbrake::Filters::GitLastCheckoutFilter))
183
+ described_class.configure { |c| c.root_directory = '/my/path' }
184
+ end
185
+
186
+ it "initializes git last checkout filter with correct root directory" do
187
+ expect(Airbrake::Filters::GitLastCheckoutFilter)
188
+ .to receive(:new).with('/my/path')
189
+ described_class.configure { |c| c.root_directory = '/my/path' }
190
+ end
191
+ end
192
+ end
193
+
194
+ describe ".notify_request" do
195
+ context "when :stash key is not provided" do
196
+ it "doesn't add anything to the stash of the request" do
197
+ expect(described_class.performance_notifier).to receive(:notify) do |request|
198
+ expect(request.stash).to be_empty
199
+ end
200
+
201
+ described_class.notify_request(
202
+ method: 'GET',
203
+ route: '/',
204
+ status_code: 200,
205
+ timing: 1,
206
+ )
207
+ end
208
+ end
209
+
210
+ context "when :stash key is provided" do
211
+ it "adds the value as the stash of the request" do
212
+ expect(described_class.performance_notifier).to receive(:notify) do |request|
213
+ expect(request.stash).to eq(request_id: 1)
214
+ end
215
+
216
+ described_class.notify_request(
217
+ {
218
+ method: 'GET',
219
+ route: '/',
220
+ status_code: 200,
221
+ timing: 1,
222
+ },
223
+ request_id: 1,
224
+ )
225
+ end
226
+ end
227
+ end
228
+
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
246
+ context "when :stash key is not provided" do
247
+ it "doesn't add anything to the stash of the query" do
248
+ expect(described_class.performance_notifier).to receive(:notify) do |query|
249
+ expect(query.stash).to be_empty
250
+ end
251
+
252
+ described_class.notify_query(
253
+ method: 'GET',
254
+ route: '/',
255
+ query: '',
256
+ timing: 1,
257
+ )
258
+ end
259
+ end
260
+
261
+ context "when :stash key is provided" do
262
+ it "adds the value as the stash of the query" do
263
+ expect(described_class.performance_notifier).to receive(:notify) do |query|
264
+ expect(query.stash).to eq(request_id: 1)
265
+ end
266
+
267
+ described_class.notify_query(
268
+ {
269
+ method: 'GET',
270
+ route: '/',
271
+ query: '',
272
+ timing: 1,
273
+ },
274
+ request_id: 1,
275
+ )
276
+ end
277
+ end
278
+ end
279
+
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
297
+ context "when :stash key is not provided" do
298
+ it "doesn't add anything to the stash of the performance breakdown" do
299
+ expect(described_class.performance_notifier).to receive(:notify) do |query|
300
+ expect(query.stash).to be_empty
301
+ end
302
+
303
+ described_class.notify_query(
304
+ method: 'GET',
305
+ route: '/',
306
+ query: '',
307
+ timing: 1,
308
+ )
309
+ end
310
+ end
311
+
312
+ context "when :stash key is provided" do
313
+ it "adds the value as the stash of the performance breakdown" do
314
+ expect(
315
+ described_class.performance_notifier,
316
+ ).to receive(:notify) do |performance_breakdown|
317
+ expect(performance_breakdown.stash).to eq(request_id: 1)
318
+ end
319
+
320
+ described_class.notify_performance_breakdown(
321
+ {
322
+ method: 'GET',
323
+ route: '/',
324
+ response_type: :html,
325
+ groups: {},
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,
375
+ },
376
+ request_id: 1,
377
+ )
378
+ end
379
+ end
380
+ end
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
+
396
+ describe ".performance_notifier" do
397
+ it "returns a performance notifier" do
398
+ expect(described_class.performance_notifier)
399
+ .to be_an(Airbrake::PerformanceNotifier)
400
+ end
401
+ end
402
+
403
+ describe ".notice_notifier" do
404
+ it "returns a notice notifier" do
405
+ expect(described_class.notice_notifier).to be_an(Airbrake::NoticeNotifier)
406
+ end
407
+ end
408
+
409
+ describe ".deploy_notifier" do
410
+ it "returns a deploy notifier" do
411
+ expect(described_class.deploy_notifier).to be_an(Airbrake::DeployNotifier)
412
+ end
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)
30
449
  end
31
450
  end
32
451
 
33
- context "when user config doesn't contain a project key" do
34
- it "raises error" do
35
- expect { described_class.configure { |c| c.project_id = 1 } }
36
- .to raise_error(Airbrake::Error, ':project_key is required')
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)
37
456
  end
38
457
  end
39
458
  end
@@ -8,140 +8,56 @@ RSpec.describe Airbrake::AsyncSender do
8
8
  Airbrake::Config.instance = Airbrake::Config.new(
9
9
  project_id: '1',
10
10
  workers: 3,
11
- queue_size: queue_size
11
+ queue_size: 10,
12
12
  )
13
-
14
- allow(Airbrake::Loggable.instance).to receive(:debug)
15
- expect(subject).to have_workers
16
13
  end
17
14
 
18
15
  describe "#send" do
19
- it "sends payload to Airbrake" do
20
- 2.times do
21
- subject.send(notice, Airbrake::Promise.new)
22
- end
23
- subject.close
24
-
25
- expect(a_request(:post, endpoint)).to have_been_made.twice
26
- end
27
-
28
- context "when the queue is full" do
29
- before do
30
- allow(subject.unsent).to receive(:size).and_return(queue_size)
31
- end
32
-
33
- it "discards payload" do
34
- 200.times do
35
- subject.send(notice, Airbrake::Promise.new)
36
- end
16
+ context "when sender has the capacity to send" do
17
+ it "sends notices to Airbrake" do
18
+ 2.times { subject.send(notice, Airbrake::Promise.new) }
37
19
  subject.close
38
20
 
39
- expect(a_request(:post, endpoint)).not_to have_been_made
21
+ expect(a_request(:post, endpoint)).to have_been_made.twice
40
22
  end
41
23
 
42
- it "logs discarded payload" do
43
- expect(Airbrake::Loggable.instance).to receive(:error).with(
44
- /reached its capacity/
45
- ).exactly(15).times
46
-
47
- 15.times do
48
- subject.send(notice, Airbrake::Promise.new)
49
- end
24
+ it "returns a resolved promise" do
25
+ promise = Airbrake::Promise.new
26
+ subject.send(notice, promise)
50
27
  subject.close
51
- end
52
- end
53
- end
54
-
55
- describe "#close" do
56
- context "when there are no unsent notices" do
57
- it "joins the spawned thread" do
58
- workers = subject.workers.list
59
- expect(workers).to all(be_alive)
60
28
 
61
- subject.close
62
- expect(workers).to all(be_stop)
29
+ expect(promise).to be_resolved
63
30
  end
64
31
  end
65
32
 
66
- context "when there are some unsent notices" do
67
- it "logs how many notices are left to send" do
68
- expect(Airbrake::Loggable.instance).to receive(:debug).with(
69
- /waiting to send \d+ unsent notice\(s\)/
33
+ context "when sender has exceeded the capacity to send" do
34
+ before do
35
+ Airbrake::Config.instance = Airbrake::Config.new(
36
+ project_id: '1',
37
+ workers: 0,
38
+ queue_size: 1,
70
39
  )
71
- expect(Airbrake::Loggable.instance).to receive(:debug).with(/closed/)
72
-
73
- 300.times { subject.send(notice, Airbrake::Promise.new) }
74
- subject.close
75
40
  end
76
41
 
77
- it "waits until the unsent notices queue is empty" do
42
+ it "doesn't send the exceeded notices to Airbrake" do
43
+ 15.times { subject.send(notice, Airbrake::Promise.new) }
78
44
  subject.close
79
- expect(subject.unsent.size).to be_zero
80
- end
81
- end
82
45
 
83
- context "when it was already closed" do
84
- it "doesn't increase the unsent queue size" do
85
- begin
86
- subject.close
87
- rescue Airbrake::Error
88
- nil
89
- end
90
-
91
- expect(subject.unsent.size).to be_zero
46
+ expect(a_request(:post, endpoint)).not_to have_been_made
92
47
  end
93
48
 
94
- it "raises error" do
49
+ it "returns a rejected promise" do
50
+ promise = nil
51
+ 15.times do
52
+ promise = subject.send(notice, Airbrake::Promise.new)
53
+ end
95
54
  subject.close
96
55
 
97
- expect(subject).to be_closed
98
- expect { subject.close }.to raise_error(
99
- Airbrake::Error, 'attempted to close already closed sender'
56
+ expect(promise).to be_rejected
57
+ expect(promise.value).to eq(
58
+ 'error' => "AsyncSender has reached its capacity of 1",
100
59
  )
101
60
  end
102
61
  end
103
-
104
- context "when workers were not spawned" do
105
- it "correctly closes the notifier nevertheless" do
106
- subject.close
107
- expect(subject).to be_closed
108
- end
109
- end
110
- end
111
-
112
- describe "#has_workers?" do
113
- it "returns false when the sender is not closed, but has 0 workers" do
114
- subject.workers.list.each do |worker|
115
- worker.kill.join
116
- end
117
- expect(subject).not_to have_workers
118
- end
119
-
120
- it "returns false when the sender is closed" do
121
- subject.close
122
- expect(subject).not_to have_workers
123
- end
124
-
125
- it "respawns workers on fork()", skip: %w[jruby rbx].include?(RUBY_ENGINE) do
126
- pid = fork { expect(subject).to have_workers }
127
- Process.wait(pid)
128
- subject.close
129
- expect(subject).not_to have_workers
130
- end
131
- end
132
-
133
- describe "#spawn_workers" do
134
- it "spawns alive threads in an enclosed ThreadGroup" do
135
- expect(subject.workers).to be_a(ThreadGroup)
136
- expect(subject.workers.list).to all(be_alive)
137
- expect(subject.workers).to be_enclosed
138
-
139
- subject.close
140
- end
141
-
142
- it "spawns exactly config.workers workers" do
143
- expect(subject.workers.list.size).to eq(Airbrake::Config.instance.workers)
144
- subject.close
145
- end
146
62
  end
147
63
  end