airbrake-ruby 4.1.0 → 5.0.0

Sign up to get free protection for your applications and to get access to all the features.
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