airbrake-ruby 5.2.0 → 5.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (66) hide show
  1. checksums.yaml +4 -4
  2. data/lib/airbrake-ruby.rb +3 -2
  3. data/lib/airbrake-ruby/async_sender.rb +3 -1
  4. data/lib/airbrake-ruby/context.rb +51 -0
  5. data/lib/airbrake-ruby/filter_chain.rb +2 -0
  6. data/lib/airbrake-ruby/filters/context_filter.rb +4 -5
  7. data/lib/airbrake-ruby/filters/exception_attributes_filter.rb +1 -1
  8. data/lib/airbrake-ruby/filters/git_last_checkout_filter.rb +1 -1
  9. data/lib/airbrake-ruby/filters/git_revision_filter.rb +1 -1
  10. data/lib/airbrake-ruby/filters/keys_filter.rb +2 -2
  11. data/lib/airbrake-ruby/filters/sql_filter.rb +2 -2
  12. data/lib/airbrake-ruby/filters/thread_filter.rb +1 -1
  13. data/lib/airbrake-ruby/ignorable.rb +0 -2
  14. data/lib/airbrake-ruby/notice_notifier.rb +3 -4
  15. data/lib/airbrake-ruby/performance_notifier.rb +1 -2
  16. data/lib/airbrake-ruby/remote_settings/settings_data.rb +1 -1
  17. data/lib/airbrake-ruby/tdigest.rb +7 -6
  18. data/lib/airbrake-ruby/thread_pool.rb +5 -3
  19. data/lib/airbrake-ruby/timed_trace.rb +1 -3
  20. data/lib/airbrake-ruby/version.rb +1 -1
  21. data/spec/airbrake_spec.rb +139 -76
  22. data/spec/async_sender_spec.rb +10 -8
  23. data/spec/backtrace_spec.rb +13 -10
  24. data/spec/benchmark_spec.rb +5 -3
  25. data/spec/code_hunk_spec.rb +24 -15
  26. data/spec/config/processor_spec.rb +12 -4
  27. data/spec/config/validator_spec.rb +5 -2
  28. data/spec/config_spec.rb +24 -16
  29. data/spec/context_spec.rb +54 -0
  30. data/spec/deploy_notifier_spec.rb +6 -4
  31. data/spec/file_cache_spec.rb +1 -0
  32. data/spec/filter_chain_spec.rb +29 -24
  33. data/spec/filters/context_filter_spec.rb +14 -5
  34. data/spec/filters/dependency_filter_spec.rb +3 -1
  35. data/spec/filters/exception_attributes_filter_spec.rb +5 -3
  36. data/spec/filters/gem_root_filter_spec.rb +5 -2
  37. data/spec/filters/git_last_checkout_filter_spec.rb +10 -12
  38. data/spec/filters/git_repository_filter.rb +9 -9
  39. data/spec/filters/git_revision_filter_spec.rb +19 -19
  40. data/spec/filters/keys_allowlist_spec.rb +25 -16
  41. data/spec/filters/keys_blocklist_spec.rb +25 -18
  42. data/spec/filters/root_directory_filter_spec.rb +3 -3
  43. data/spec/filters/sql_filter_spec.rb +26 -26
  44. data/spec/filters/system_exit_filter_spec.rb +4 -2
  45. data/spec/filters/thread_filter_spec.rb +15 -13
  46. data/spec/loggable_spec.rb +2 -2
  47. data/spec/monotonic_time_spec.rb +8 -6
  48. data/spec/nested_exception_spec.rb +46 -46
  49. data/spec/notice_notifier/options_spec.rb +23 -13
  50. data/spec/notice_notifier_spec.rb +52 -47
  51. data/spec/notice_spec.rb +6 -2
  52. data/spec/performance_notifier_spec.rb +67 -60
  53. data/spec/promise_spec.rb +38 -32
  54. data/spec/remote_settings/callback_spec.rb +27 -8
  55. data/spec/remote_settings/settings_data_spec.rb +4 -4
  56. data/spec/remote_settings_spec.rb +18 -8
  57. data/spec/response_spec.rb +34 -12
  58. data/spec/stashable_spec.rb +5 -5
  59. data/spec/stat_spec.rb +7 -5
  60. data/spec/sync_sender_spec.rb +49 -16
  61. data/spec/tdigest_spec.rb +60 -55
  62. data/spec/thread_pool_spec.rb +65 -55
  63. data/spec/time_truncate_spec.rb +4 -2
  64. data/spec/timed_trace_spec.rb +32 -30
  65. data/spec/truncator_spec.rb +72 -43
  66. metadata +51 -48
@@ -5,19 +5,19 @@ RSpec.describe Airbrake::Stashable do
5
5
  end
6
6
 
7
7
  describe "#stash" do
8
- subject { klass.new }
8
+ subject(:instance) { klass.new }
9
9
 
10
10
  it "returns a hash" do
11
- expect(subject.stash).to be_a(Hash)
11
+ expect(instance.stash).to be_a(Hash)
12
12
  end
13
13
 
14
14
  it "returns an empty hash" do
15
- expect(subject.stash).to be_empty
15
+ expect(instance.stash).to be_empty
16
16
  end
17
17
 
18
18
  it "remembers what was put in the stash" do
19
- subject.stash[:foo] = 1
20
- expect(subject.stash[:foo]).to eq(1)
19
+ instance.stash[:foo] = 1
20
+ expect(instance.stash[:foo]).to eq(1)
21
21
  end
22
22
  end
23
23
  end
data/spec/stat_spec.rb CHANGED
@@ -1,7 +1,9 @@
1
1
  RSpec.describe Airbrake::Stat do
2
+ subject(:stat) { described_class.new }
3
+
2
4
  describe "#to_h" do
3
5
  it "converts to a hash" do
4
- expect(subject.to_h).to eq(
6
+ expect(stat.to_h).to eq(
5
7
  'count' => 0,
6
8
  'sum' => 0.0,
7
9
  'sumsq' => 0.0,
@@ -11,19 +13,19 @@ RSpec.describe Airbrake::Stat do
11
13
  end
12
14
 
13
15
  describe "#increment_ms" do
14
- before { subject.increment_ms(1000) }
16
+ before { stat.increment_ms(1000) }
15
17
 
16
18
  its(:sum) { is_expected.to eq(1000) }
17
19
  its(:sumsq) { is_expected.to eq(1000000) }
18
20
 
19
21
  it "updates tdigest" do
20
- expect(subject.tdigest.size).to eq(1)
22
+ expect(stat.tdigest.size).to eq(1)
21
23
  end
22
24
  end
23
25
 
24
26
  describe "#inspect" do
25
27
  it "provides custom inspect output" do
26
- expect(subject.inspect).to eq(
28
+ expect(stat.inspect).to eq(
27
29
  '#<struct Airbrake::Stat count=0, sum=0.0, sumsq=0.0>',
28
30
  )
29
31
  end
@@ -31,7 +33,7 @@ RSpec.describe Airbrake::Stat do
31
33
 
32
34
  describe "#pretty_print" do
33
35
  it "is an alias of #inspect" do
34
- expect(subject.method(:pretty_print)).to eql(subject.method(:inspect))
36
+ expect(stat.method(:pretty_print)).to eql(stat.method(:inspect))
35
37
  end
36
38
  end
37
39
  end
@@ -1,4 +1,6 @@
1
1
  RSpec.describe Airbrake::SyncSender do
2
+ subject(:sync_sender) { described_class.new }
3
+
2
4
  before do
3
5
  Airbrake::Config.instance = Airbrake::Config.new(
4
6
  project_id: 1, project_key: 'banana',
@@ -14,7 +16,7 @@ RSpec.describe Airbrake::SyncSender do
14
16
  before { stub_request(:post, endpoint).to_return(body: '{}') }
15
17
 
16
18
  it "sets the Content-Type header to JSON" do
17
- subject.send({}, promise)
19
+ sync_sender.send({}, promise)
18
20
  expect(
19
21
  a_request(:post, endpoint).with(
20
22
  headers: { 'Content-Type' => 'application/json' },
@@ -23,7 +25,7 @@ RSpec.describe Airbrake::SyncSender do
23
25
  end
24
26
 
25
27
  it "sets the User-Agent header to the notifier slug" do
26
- subject.send({}, promise)
28
+ sync_sender.send({}, promise)
27
29
  expect(
28
30
  a_request(:post, endpoint).with(
29
31
  headers: {
@@ -36,7 +38,7 @@ RSpec.describe Airbrake::SyncSender do
36
38
  end
37
39
 
38
40
  it "sets the Authorization header to the project key" do
39
- subject.send({}, promise)
41
+ sync_sender.send({}, promise)
40
42
  expect(
41
43
  a_request(:post, endpoint).with(
42
44
  headers: { 'Authorization' => 'Bearer banana' },
@@ -45,19 +47,46 @@ RSpec.describe Airbrake::SyncSender do
45
47
  end
46
48
 
47
49
  it "catches exceptions raised while sending" do
50
+ # rubocop:disable RSpec/VerifiedDoubles
48
51
  https = double("foo")
49
- allow(subject).to receive(:build_https).and_return(https)
52
+ # rubocop:enable RSpec/VerifiedDoubles
53
+
54
+ # rubocop:disable RSpec/SubjectStub
55
+ allow(sync_sender).to receive(:build_https).and_return(https)
56
+ # rubocop:enable RSpec/SubjectStub
57
+
50
58
  allow(https).to receive(:request).and_raise(StandardError.new('foo'))
51
- expect(Airbrake::Loggable.instance).to receive(:error).with(
59
+
60
+ expect(sync_sender.send({}, promise)).to be_an(Airbrake::Promise)
61
+ expect(promise.value).to eq('error' => '**Airbrake: HTTP error: foo')
62
+ end
63
+
64
+ it "logs exceptions raised while sending" do
65
+ allow(Airbrake::Loggable.instance).to receive(:error)
66
+
67
+ # rubocop:disable RSpec/VerifiedDoubles
68
+ https = double("foo")
69
+ # rubocop:enable RSpec/VerifiedDoubles
70
+
71
+ # rubocop:disable RSpec/SubjectStub
72
+ allow(sync_sender).to receive(:build_https).and_return(https)
73
+ # rubocop:enable RSpec/SubjectStub
74
+
75
+ allow(https).to receive(:request).and_raise(StandardError.new('foo'))
76
+
77
+ sync_sender.send({}, promise)
78
+
79
+ expect(Airbrake::Loggable.instance).to have_received(:error).with(
52
80
  /HTTP error: foo/,
53
81
  )
54
- expect(subject.send({}, promise)).to be_an(Airbrake::Promise)
55
- expect(promise.value).to eq('error' => '**Airbrake: HTTP error: foo')
56
82
  end
57
83
 
58
84
  context "when request body is nil" do
85
+ # rubocop:disable RSpec/MultipleExpectations
59
86
  it "doesn't send data" do
60
- expect_any_instance_of(Airbrake::Truncator)
87
+ allow(Airbrake::Loggable.instance).to receive(:error)
88
+
89
+ allow_any_instance_of(Airbrake::Truncator)
61
90
  .to receive(:reduce_max_size).and_return(0)
62
91
 
63
92
  encoded = Base64.encode64("\xD3\xE6\xBC\x9D\xBA").encode!('ASCII-8BIT')
@@ -70,16 +99,18 @@ RSpec.describe Airbrake::SyncSender do
70
99
 
71
100
  notice = Airbrake::Notice.new(ex)
72
101
 
73
- expect(Airbrake::Loggable.instance).to receive(:error).with(
102
+ expect(sync_sender.send(notice, promise)).to be_an(Airbrake::Promise)
103
+ expect(promise.value)
104
+ .to match('error' => '**Airbrake: data was not sent because of missing body')
105
+
106
+ expect(Airbrake::Loggable.instance).to have_received(:error).with(
74
107
  /data was not sent/,
75
108
  )
76
- expect(Airbrake::Loggable.instance).to receive(:error).with(
109
+ expect(Airbrake::Loggable.instance).to have_received(:error).with(
77
110
  /truncation failed/,
78
111
  )
79
- expect(subject.send(notice, promise)).to be_an(Airbrake::Promise)
80
- expect(promise.value)
81
- .to match('error' => '**Airbrake: data was not sent because of missing body')
82
112
  end
113
+ # rubocop:enable RSpec/MultipleExpectations
83
114
  end
84
115
 
85
116
  context "when IP is rate limited" do
@@ -93,13 +124,14 @@ RSpec.describe Airbrake::SyncSender do
93
124
  )
94
125
  end
95
126
 
127
+ # rubocop:disable RSpec/MultipleExpectations
96
128
  it "returns error" do
97
129
  p1 = Airbrake::Promise.new
98
- subject.send({}, p1)
130
+ sync_sender.send({}, p1)
99
131
  expect(p1.value).to match('error' => '**Airbrake: IP is rate limited')
100
132
 
101
133
  p2 = Airbrake::Promise.new
102
- subject.send({}, p2)
134
+ sync_sender.send({}, p2)
103
135
  expect(p2.value).to match('error' => '**Airbrake: IP is rate limited')
104
136
 
105
137
  # Wait for X-RateLimit-Delay and then make a new request to make sure p2
@@ -107,11 +139,12 @@ RSpec.describe Airbrake::SyncSender do
107
139
  sleep 1
108
140
 
109
141
  p3 = Airbrake::Promise.new
110
- subject.send({}, p3)
142
+ sync_sender.send({}, p3)
111
143
  expect(p3.value).to match('error' => '**Airbrake: IP is rate limited')
112
144
 
113
145
  expect(a_request(:post, endpoint)).to have_been_made.twice
114
146
  end
147
+ # rubocop:enable RSpec/MultipleExpectations
115
148
  end
116
149
 
117
150
  context "when the provided method is :put" do
data/spec/tdigest_spec.rb CHANGED
@@ -1,16 +1,18 @@
1
1
  RSpec.describe Airbrake::TDigest do
2
+ subject(:tdigest) { described_class.new }
3
+
2
4
  describe "byte serialization" do
3
5
  it "loads serialized data" do
4
- subject.push(60, 100)
5
- 10.times { subject.push(rand * 100) }
6
- bytes = subject.as_bytes
6
+ tdigest.push(60, 100)
7
+ 10.times { tdigest.push(rand * 100) }
8
+ bytes = tdigest.as_bytes
7
9
  new_tdigest = described_class.from_bytes(bytes)
8
- expect(new_tdigest.percentile(0.9)).to eq(subject.percentile(0.9))
10
+ expect(new_tdigest.percentile(0.9)).to eq(tdigest.percentile(0.9))
9
11
  expect(new_tdigest.as_bytes).to eq(bytes)
10
12
  end
11
13
 
12
14
  it "handles zero size" do
13
- bytes = subject.as_bytes
15
+ bytes = tdigest.as_bytes
14
16
  expect(described_class.from_bytes(bytes).size).to be_zero
15
17
  end
16
18
 
@@ -24,65 +26,64 @@ RSpec.describe Airbrake::TDigest do
24
26
 
25
27
  describe "small byte serialization" do
26
28
  it "loads serialized data" do
27
- 10.times { subject.push(10) }
28
- bytes = subject.as_small_bytes
29
+ 10.times { tdigest.push(10) }
30
+ bytes = tdigest.as_small_bytes
29
31
  new_tdigest = described_class.from_bytes(bytes)
30
32
  # Expect some rounding error due to compression
31
33
  expect(new_tdigest.percentile(0.9).round(5)).to eq(
32
- subject.percentile(0.9).round(5),
34
+ tdigest.percentile(0.9).round(5),
33
35
  )
34
36
  expect(new_tdigest.as_small_bytes).to eq(bytes)
35
37
  end
36
38
 
37
39
  it "handles zero size" do
38
- bytes = subject.as_small_bytes
40
+ bytes = tdigest.as_small_bytes
39
41
  expect(described_class.from_bytes(bytes).size).to be_zero
40
42
  end
41
43
  end
42
44
 
43
45
  describe "JSON serialization" do
44
46
  it "loads serialized data" do
45
- subject.push(60, 100)
46
- json = subject.as_json
47
+ tdigest.push(60, 100)
48
+ json = tdigest.as_json
47
49
  new_tdigest = described_class.from_json(json)
48
- expect(new_tdigest.percentile(0.9)).to eq(subject.percentile(0.9))
50
+ expect(new_tdigest.percentile(0.9)).to eq(tdigest.percentile(0.9))
49
51
  end
50
52
  end
51
53
 
52
54
  describe "#percentile" do
53
55
  it "returns nil if empty" do
54
- expect(subject.percentile(0.90)).to be_nil # This should not crash
56
+ expect(tdigest.percentile(0.90)).to be_nil # This should not crash
55
57
  end
56
58
 
57
59
  it "raises ArgumentError of input not between 0 and 1" do
58
- expect { subject.percentile(1.1) }.to raise_error(ArgumentError)
60
+ expect { tdigest.percentile(1.1) }.to raise_error(ArgumentError)
59
61
  end
60
62
 
61
63
  describe "with only single value" do
62
64
  it "returns the value" do
63
- subject.push(60, 100)
64
- expect(subject.percentile(0.90)).to eq(60)
65
+ tdigest.push(60, 100)
66
+ expect(tdigest.percentile(0.90)).to eq(60)
65
67
  end
66
68
 
67
69
  it "returns 0 for all percentiles when only 0 present" do
68
- subject.push(0)
69
- expect(subject.percentile([0.0, 0.5, 1.0])).to eq([0, 0, 0])
70
+ tdigest.push(0)
71
+ expect(tdigest.percentile([0.0, 0.5, 1.0])).to eq([0, 0, 0])
70
72
  end
71
73
  end
72
74
 
73
75
  describe "with alot of uniformly distributed points" do
74
76
  it "has minimal error" do
75
77
  seed = srand(1234) # Makes the values a proper fixture
76
- N = 100_000
77
78
  maxerr = 0
78
- values = Array.new(N).map { rand }
79
+ values = Array.new(100_000).map { rand }
79
80
  srand(seed)
80
81
 
81
- subject.push(values)
82
- subject.compress!
82
+ tdigest.push(values)
83
+ tdigest.compress!
83
84
 
84
85
  0.step(1, 0.1).each do |i|
85
- q = subject.percentile(i)
86
+ q = tdigest.percentile(i)
86
87
  maxerr = [maxerr, (i - q).abs].max
87
88
  end
88
89
 
@@ -93,7 +94,7 @@ RSpec.describe Airbrake::TDigest do
93
94
 
94
95
  describe "#push" do
95
96
  it "calls _cumulate so won't crash because of uninitialized mean_cumn" do
96
- subject.push(
97
+ tdigest.push(
97
98
  [
98
99
  125000000.0,
99
100
  104166666.66666666,
@@ -132,59 +133,62 @@ RSpec.describe Airbrake::TDigest do
132
133
  end
133
134
 
134
135
  it "does not blow up if data comes in sorted" do
135
- subject.push(0..10_000)
136
- expect(subject.centroids.size).to be < 5_000
137
- subject.compress!
138
- expect(subject.centroids.size).to be < 1_000
136
+ tdigest.push(0..10_000)
137
+ expect(tdigest.centroids.size).to be < 5_000
138
+ tdigest.compress!
139
+ expect(tdigest.centroids.size).to be < 1_000
139
140
  end
140
141
  end
141
142
 
142
143
  describe "#size" do
143
144
  it "reports the number of observations" do
144
145
  n = 10_000
145
- n.times { subject.push(rand) }
146
- subject.compress!
147
- expect(subject.size).to eq(n)
146
+ n.times { tdigest.push(rand) }
147
+ tdigest.compress!
148
+ expect(tdigest.size).to eq(n)
148
149
  end
149
150
  end
150
151
 
151
152
  describe "#+" do
152
153
  it "works with empty tdigests" do
153
154
  other = described_class.new(0.001, 50, 1.2)
154
- expect((subject + other).centroids.size).to eq(0)
155
+ expect((tdigest + other).centroids.size).to eq(0)
155
156
  end
156
157
 
157
158
  describe "adding two tdigests" do
159
+ let(:other) { described_class.new(0.001, 50, 1.2) }
160
+
158
161
  before do
159
- @other = described_class.new(0.001, 50, 1.2)
160
- [subject, @other].each do |td|
162
+ [tdigest, other].each do |td|
161
163
  td.push(60, 100)
162
164
  10.times { td.push(rand * 100) }
163
165
  end
164
166
  end
165
167
 
168
+ # rubocop:disable RSpec/MultipleExpectations
166
169
  it "has the parameters of the left argument (the calling tdigest)" do
167
- new_tdigest = subject + @other
170
+ new_tdigest = tdigest + other
168
171
  expect(new_tdigest.instance_variable_get(:@delta)).to eq(
169
- subject.instance_variable_get(:@delta),
172
+ tdigest.instance_variable_get(:@delta),
170
173
  )
171
174
  expect(new_tdigest.instance_variable_get(:@k)).to eq(
172
- subject.instance_variable_get(:@k),
175
+ tdigest.instance_variable_get(:@k),
173
176
  )
174
177
  expect(new_tdigest.instance_variable_get(:@cx)).to eq(
175
- subject.instance_variable_get(:@cx),
178
+ tdigest.instance_variable_get(:@cx),
176
179
  )
177
180
  end
181
+ # rubocop:enable RSpec/MultipleExpectations
178
182
 
179
183
  it "returns a tdigest with less than or equal centroids" do
180
- new_tdigest = subject + @other
184
+ new_tdigest = tdigest + other
181
185
  expect(new_tdigest.centroids.size)
182
- .to be <= subject.centroids.size + @other.centroids.size
186
+ .to be <= tdigest.centroids.size + other.centroids.size
183
187
  end
184
188
 
185
189
  it "has the size of the two digests combined" do
186
- new_tdigest = subject + @other
187
- expect(new_tdigest.size).to eq(subject.size + @other.size)
190
+ new_tdigest = tdigest + other
191
+ expect(new_tdigest.size).to eq(tdigest.size + other.size)
188
192
  end
189
193
  end
190
194
  end
@@ -192,14 +196,15 @@ RSpec.describe Airbrake::TDigest do
192
196
  describe "#merge!" do
193
197
  it "works with empty tdigests" do
194
198
  other = described_class.new(0.001, 50, 1.2)
195
- subject.merge!(other)
196
- expect(subject.centroids.size).to be_zero
199
+ tdigest.merge!(other)
200
+ expect(tdigest.centroids.size).to be_zero
197
201
  end
198
202
 
199
203
  describe "with populated tdigests" do
204
+ let(:other) { described_class.new(0.001, 50, 1.2) }
205
+
200
206
  before do
201
- @other = described_class.new(0.001, 50, 1.2)
202
- [subject, @other].each do |td|
207
+ [tdigest, other].each do |td|
203
208
  td.push(60, 100)
204
209
  10.times { td.push(rand * 100) }
205
210
  end
@@ -207,23 +212,23 @@ RSpec.describe Airbrake::TDigest do
207
212
 
208
213
  it "has the parameters of the calling tdigest" do
209
214
  vars = %i[@delta @k @cx]
210
- expected = Hash[vars.map { |v| [v, subject.instance_variable_get(v)] }]
211
- subject.merge!(@other)
215
+ expected = vars.map { |v| [v, tdigest.instance_variable_get(v)] }.to_h
216
+ tdigest.merge!(other)
212
217
  vars.each do |v|
213
- expect(subject.instance_variable_get(v)).to eq(expected[v])
218
+ expect(tdigest.instance_variable_get(v)).to eq(expected[v])
214
219
  end
215
220
  end
216
221
 
217
222
  it "returns a tdigest with less than or equal centroids" do
218
- combined_size = subject.centroids.size + @other.centroids.size
219
- subject.merge!(@other)
220
- expect(subject.centroids.size).to be <= combined_size
223
+ combined_size = tdigest.centroids.size + other.centroids.size
224
+ tdigest.merge!(other)
225
+ expect(tdigest.centroids.size).to be <= combined_size
221
226
  end
222
227
 
223
228
  it "has the size of the two digests combined" do
224
- combined_size = subject.size + @other.size
225
- subject.merge!(@other)
226
- expect(subject.size).to eq(combined_size)
229
+ combined_size = tdigest.size + other.size
230
+ tdigest.merge!(other)
231
+ expect(tdigest.size).to eq(combined_size)
227
232
  end
228
233
  end
229
234
  end