airbrake-ruby 2.5.1 → 2.6.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: c824b0d3f9372ab651d5e308df76080b2000f519
4
- data.tar.gz: 2fc17c502f0bd474db41d00697a12bcdfeb0ac6e
3
+ metadata.gz: 10cd1c4c112ec8325d9e800128cb3598ecca5e79
4
+ data.tar.gz: e06b612001f91e0a6e928250d735fc57b2d9a313
5
5
  SHA512:
6
- metadata.gz: 191e18fafeddbfe6501f5acdf17cd5ec9fd759e41e78b2c3637625b87024561d2ab9599292c420607051b0043079f88490d8470f6caebe6e5e431640a5bdcf43
7
- data.tar.gz: 1d16bdffa0fd2c37184b2e95be230b6816e3e9b5d0c2f01b09c9867d70e8d0551f73b9001de35eb0529a0104b7d59b1c86a6fa8236bc7d742bad2101e9844899
6
+ metadata.gz: 39edba81094187bde03fd1f5fc916255737dde0fabcd0f5c720075c22c97d914c602bdda046669db3b48680ec0bae798bf4225a4645d4a08e450171de76a34d0
7
+ data.tar.gz: 3d0b4de47de97f5cc9bd425a70aa1128449df90329f33e93f843c6729571cd2ca38d371477fa98ea46d380c116dd7bf20a670f230bef59eeb34d8fd72d1b0f23
@@ -180,7 +180,9 @@ module Airbrake
180
180
  end
181
181
 
182
182
  def truncate
183
- TRUNCATABLE_KEYS.each { |key| @truncator.truncate_object(self[key]) }
183
+ TRUNCATABLE_KEYS.each do |key|
184
+ @payload[key] = @truncator.truncate(@payload[key])
185
+ end
184
186
 
185
187
  new_max_size = @truncator.reduce_max_size
186
188
  if new_max_size == 0
@@ -1,55 +1,34 @@
1
1
  module Airbrake
2
- ##
3
2
  # This class is responsible for truncation of too big objects. Mainly, you
4
3
  # should use it for simple objects such as strings, hashes, & arrays.
5
4
  #
6
5
  # @api private
7
6
  # @since v1.0.0
8
7
  class Truncator
9
- ##
10
8
  # @return [Hash] the options for +String#encode+
11
9
  ENCODING_OPTIONS = { invalid: :replace, undef: :replace }.freeze
12
10
 
13
- ##
14
11
  # @return [String] the temporary encoding to be used when fixing invalid
15
12
  # strings with +ENCODING_OPTIONS+
16
13
  TEMP_ENCODING = 'utf-16'.freeze
17
14
 
18
- ##
19
15
  # @param [Integer] max_size maximum size of hashes, arrays and strings
20
16
  def initialize(max_size)
21
17
  @max_size = max_size
22
18
  end
23
19
 
24
- ##
25
- # Performs deep truncation of arrays, hashes and sets. Uses a
20
+ # Performs deep truncation of arrays, hashes, sets & strings. Uses a
26
21
  # placeholder for recursive objects (`[Circular]`).
27
22
  #
28
- # @param [Hash,Array] object The object to truncate
29
- # @param [Hash] seen The hash that helps to detect recursion
30
- # @return [void]
31
- # @note This method is public to simplify testing. You probably want to use
32
- # {truncate_notice} instead
33
- def truncate_object(object, seen = {})
34
- return seen[object] if seen[object]
35
-
36
- seen[object] = '[Circular]'.freeze
37
- truncated =
38
- if object.is_a?(Hash)
39
- truncate_hash(object, seen)
40
- elsif object.is_a?(Array)
41
- truncate_array(object, seen)
42
- elsif object.is_a?(Set)
43
- truncate_set(object, seen)
44
- else
45
- raise Airbrake::Error,
46
- "cannot truncate object: #{object} (#{object.class})"
47
- end
48
- seen[object] = truncated
23
+ # @param [Object] object The object to truncate
24
+ # @param [Set] seen The cache that helps to detect recursion
25
+ # @return [Object] truncated object
26
+ def truncate(object, seen = Set.new)
27
+ return '[Circular]'.freeze if seen.include?(object)
28
+ truncate_object(object, seen << object)
49
29
  end
50
30
 
51
- ##
52
- # Reduces maximum allowed size of the truncated object.
31
+ # Reduces maximum allowed size of hashes, arrays, sets & strings by half.
53
32
  # @return [Integer] current +max_size+ value
54
33
  def reduce_max_size
55
34
  @max_size /= 2
@@ -57,67 +36,68 @@ module Airbrake
57
36
 
58
37
  private
59
38
 
60
- def truncate(val, seen)
61
- case val
62
- when String
63
- truncate_string(val)
64
- when Array, Hash, Set
65
- truncate_object(val, seen)
66
- when Numeric, TrueClass, FalseClass, Symbol, NilClass
67
- val
39
+ def truncate_object(object, seen)
40
+ case object
41
+ when Hash then truncate_hash(object, seen)
42
+ when Array then truncate_array(object, seen)
43
+ when Set then truncate_set(object, seen)
44
+ when String then truncate_string(object)
45
+ when Numeric, TrueClass, FalseClass, Symbol, NilClass then object
68
46
  else
69
- stringified_val =
70
- begin
71
- val.to_json
72
- rescue *Notice::JSON_EXCEPTIONS
73
- val.to_s
74
- end
75
- truncate_string(stringified_val)
47
+ truncate_string(stringify_object(object))
76
48
  end
77
49
  end
78
50
 
79
51
  def truncate_string(str)
80
- str = replace_invalid_characters!(str)
81
- return str if str.length <= @max_size
82
- str.slice(0, @max_size) + '[Truncated]'.freeze
52
+ fixed_str = replace_invalid_characters(str)
53
+ return fixed_str if fixed_str.length <= @max_size
54
+ (fixed_str.slice(0, @max_size) + '[Truncated]').freeze
83
55
  end
84
56
 
85
- ##
86
- # Replaces invalid characters in string with arbitrary encoding.
87
- #
88
- # @param [String] str The string to replace characters
89
- # @return [String] a UTF-8 encoded string
90
- # @note This method mutates +str+ unless it's frozen,
91
- # in which case it creates a duplicate
92
- # @see https://github.com/flori/json/commit/3e158410e81f94dbbc3da6b7b35f4f64983aa4e3
93
- def replace_invalid_characters!(str)
94
- encoding = str.encoding
95
- utf8_string = (encoding == Encoding::UTF_8 || encoding == Encoding::ASCII)
96
- return str if utf8_string && str.valid_encoding?
97
-
98
- str = str.dup if str.frozen?
99
- str.encode!(TEMP_ENCODING, ENCODING_OPTIONS) if utf8_string
100
- str.encode!('utf-8', ENCODING_OPTIONS)
57
+ def stringify_object(object)
58
+ object.to_json
59
+ rescue *Notice::JSON_EXCEPTIONS
60
+ object.to_s
101
61
  end
102
62
 
103
63
  def truncate_hash(hash, seen)
64
+ truncated_hash = {}
104
65
  hash.each_with_index do |(key, val), idx|
105
- if idx < @max_size
106
- hash[key] = truncate(val, seen)
107
- else
108
- hash.delete(key)
109
- end
66
+ break if idx + 1 > @max_size
67
+ truncated_hash[key] = truncate(val, seen)
110
68
  end
69
+
70
+ truncated_hash.freeze
111
71
  end
112
72
 
113
73
  def truncate_array(array, seen)
114
- array.slice(0, @max_size).map! { |val| truncate(val, seen) }
74
+ array.slice(0, @max_size).map! { |elem| truncate(elem, seen) }.freeze
115
75
  end
116
76
 
117
77
  def truncate_set(set, seen)
118
- set.keep_if.with_index { |_val, idx| idx < @max_size }.map! do |val|
119
- truncate(val, seen)
78
+ truncated_set = Set.new
79
+
80
+ set.each do |elem|
81
+ truncated_set << truncate(elem, seen)
82
+ break if truncated_set.size >= @max_size
120
83
  end
84
+
85
+ truncated_set.freeze
86
+ end
87
+
88
+ # Replaces invalid characters in a string with arbitrary encoding.
89
+ #
90
+ # @param [String] str The string to replace characters
91
+ # @return [String] a UTF-8 encoded string
92
+ # @see https://github.com/flori/json/commit/3e158410e81f94dbbc3da6b7b35f4f64983aa4e3
93
+ def replace_invalid_characters(str)
94
+ encoding = str.encoding
95
+ utf8_string = (encoding == Encoding::UTF_8 || encoding == Encoding::ASCII)
96
+ return str if utf8_string && str.valid_encoding?
97
+
98
+ temp_str = str.dup
99
+ temp_str.encode!(TEMP_ENCODING, ENCODING_OPTIONS) if utf8_string
100
+ temp_str.encode!('utf-8', ENCODING_OPTIONS)
121
101
  end
122
102
  end
123
103
  end
@@ -4,5 +4,5 @@
4
4
  module Airbrake
5
5
  ##
6
6
  # @return [String] the library version
7
- AIRBRAKE_RUBY_VERSION = '2.5.1'.freeze
7
+ AIRBRAKE_RUBY_VERSION = '2.6.0'.freeze
8
8
  end
@@ -180,7 +180,7 @@ module Airbrake
180
180
  end
181
181
 
182
182
  def truncate
183
- TRUNCATABLE_KEYS.each { |key| @truncator.truncate_object(self[key]) }
183
+ TRUNCATABLE_KEYS.each { |key| @truncator.truncate(self[key]) }
184
184
 
185
185
  new_max_size = @truncator.reduce_max_size
186
186
  if new_max_size == 0
@@ -1,437 +1,214 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  RSpec.describe Airbrake::Truncator do
4
- let(:max_size) { 1000 }
5
- let(:truncated_len) { '[Truncated]'.length }
6
- let(:max_len) { max_size + truncated_len }
7
-
8
- before do
9
- @truncator = described_class.new(max_size)
4
+ def multiply_by_2_max_len(chr)
5
+ chr * 2 * max_len
10
6
  end
11
7
 
12
- describe "#truncate_object" do
13
- describe "error backtrace" do
14
- let(:error) do
15
- { type: 'AirbrakeTestError', message: 'App crashed!', backtrace: [] }
16
- end
17
-
18
- before do
19
- backtrace = Array.new(size) do
20
- { file: 'foo.rb', line: 23, function: '<main>' }
21
- end
22
-
23
- @error = error.merge(backtrace: backtrace)
24
- described_class.new(max_size).truncate_object(@error)
25
- end
26
-
27
- context "when long" do
28
- let(:size) { 2003 }
8
+ describe "#truncate" do
9
+ let(:max_size) { 3 }
10
+ let(:truncated) { '[Truncated]' }
11
+ let(:max_len) { max_size + truncated.length }
29
12
 
30
- it "truncates the backtrace to the max size" do
31
- expect(@error[:backtrace].size).to eq(1000)
32
- end
33
- end
13
+ subject { described_class.new(max_size).truncate(object) }
34
14
 
35
- context "when short" do
36
- let(:size) { 999 }
15
+ context "given a frozen string" do
16
+ let(:object) { multiply_by_2_max_len('a') }
37
17
 
38
- it "does not truncate the backtrace" do
39
- expect(@error[:backtrace].size).to eq(size)
40
- end
18
+ it "returns a new truncated frozen string" do
19
+ expect(subject.length).to eq(max_len)
20
+ expect(subject).to be_frozen
41
21
  end
42
22
  end
43
23
 
44
- describe "error message" do
45
- let(:error) do
46
- { type: 'AirbrakeTestError', message: 'App crashed!', backtrace: [] }
24
+ context "given a frozen hash of strings" do
25
+ let(:object) do
26
+ {
27
+ banana: multiply_by_2_max_len('a'),
28
+ kiwi: multiply_by_2_max_len('b'),
29
+ strawberry: 'c',
30
+ shrimp: 'd'
31
+ }.freeze
47
32
  end
48
33
 
49
- before do
50
- @error = error.merge(message: message)
51
- described_class.new(max_size).truncate_object(@error)
52
- end
34
+ it "returns a new truncated frozen hash" do
35
+ expect(subject.size).to eq(max_size)
36
+ expect(subject).to be_frozen
53
37
 
54
- context "when long" do
55
- let(:message) { 'App crashed!' * 2000 }
38
+ expect(subject).to eq(
39
+ banana: 'aaa[Truncated]', kiwi: 'bbb[Truncated]', strawberry: 'c'
40
+ )
41
+ expect(subject[:banana]).to be_frozen
42
+ expect(subject[:kiwi]).to be_frozen
43
+ expect(subject[:strawberry]).not_to be_frozen
44
+ end
45
+ end
56
46
 
57
- it "truncates the message" do
58
- expect(@error[:message].length).to eq(max_len)
59
- end
47
+ context "given a frozen array of strings" do
48
+ let(:object) do
49
+ [
50
+ multiply_by_2_max_len('a'),
51
+ 'b',
52
+ multiply_by_2_max_len('c'),
53
+ 'd'
54
+ ].freeze
60
55
  end
61
56
 
62
- context "when short" do
63
- let(:message) { 'App crashed!' }
64
- let(:msg_len) { message.length }
57
+ it "returns a new truncated frozen array" do
58
+ expect(subject.size).to eq(max_size)
59
+ expect(subject).to be_frozen
65
60
 
66
- it "doesn't truncate the message" do
67
- expect(@error[:message].length).to eq(msg_len)
68
- end
61
+ expect(subject).to eq(['aaa[Truncated]', 'b', 'ccc[Truncated]'])
62
+ expect(subject[0]).to be_frozen
63
+ expect(subject[1]).not_to be_frozen
64
+ expect(subject[2]).to be_frozen
69
65
  end
70
66
  end
71
67
 
72
- describe "given a hash with short values" do
73
- let(:params) do
74
- { bingo: 'bango', bongo: 'bish', bash: 'bosh' }
68
+ context "given a frozen set of strings" do
69
+ let(:object) do
70
+ Set.new([
71
+ multiply_by_2_max_len('a'),
72
+ 'b',
73
+ multiply_by_2_max_len('c'),
74
+ 'd'
75
+ ]).freeze
75
76
  end
76
77
 
77
- it "doesn't get truncated" do
78
- @truncator.truncate_object(params)
79
- expect(params).to eq(bingo: 'bango', bongo: 'bish', bash: 'bosh')
78
+ it "returns a new truncated frozen array" do
79
+ expect(subject.size).to eq(max_size)
80
+ expect(subject).to be_frozen
81
+
82
+ expect(subject).to eq(
83
+ Set.new(['aaa[Truncated]', 'b', 'ccc[Truncated]'])
84
+ )
85
+ expect(subject).to be_frozen
80
86
  end
81
87
  end
82
88
 
83
- describe "given a hash with a lot of elements" do
84
- context "the elements of which are also hashes with a lot of elements" do
85
- let(:params) do
86
- Hash[(0...4124).each_cons(2).to_a].tap do |h|
87
- h[0] = Hash[(0...4124).each_cons(2).to_a]
88
- end
89
+ context "given an arbitrary frozen object that responds to #to_json" do
90
+ let(:object) do
91
+ obj = Object.new
92
+ def obj.to_json
93
+ '{"object":"shrimp"}'
89
94
  end
95
+ obj.freeze
96
+ end
90
97
 
91
- it "truncates all the hashes to the max allowed size" do
92
- expect(params.size).to eq(4123)
93
- expect(params[0].size).to eq(4123)
94
-
95
- @truncator.truncate_object(params)
98
+ it "converts the object to truncated JSON" do
99
+ expect(subject.length).to eq(max_len)
100
+ expect(subject).to be_frozen
96
101
 
97
- expect(params.size).to eq(1000)
98
- expect(params[0].size).to eq(1000)
99
- end
102
+ expect(subject).to eq('{"o[Truncated]')
100
103
  end
101
104
  end
102
105
 
103
- describe "given a set with a lot of elements" do
104
- context "the elements of which are also sets with a lot of elements" do
105
- let(:params) do
106
- row = (0...4124).each_cons(2)
107
- set = Set.new(row.to_a.unshift(row.to_a))
108
- { bingo: set }
109
- end
110
-
111
- it "truncates all the sets to the max allowed size" do
112
- expect(params[:bingo].size).to eq(4124)
113
- expect(params[:bingo].to_a[0].size).to eq(4123)
114
-
115
- @truncator.truncate_object(params)
116
-
117
- expect(params[:bingo].size).to eq(1000)
118
- expect(params[:bingo].to_a[0].size).to eq(1000)
119
- end
106
+ context "given an arbitrary object that doesn't respond to #to_json" do
107
+ let(:object) do
108
+ obj = Object.new
109
+ allow(obj).to receive(:to_json).
110
+ and_raise(Airbrake::Notice::JSON_EXCEPTIONS.first)
111
+ obj
120
112
  end
121
113
 
122
- context "including recursive sets" do
123
- let(:params) do
124
- a = Set.new
125
- a << a << :bango
126
- { bingo: a }
127
- end
128
-
129
- it "prevents recursion" do
130
- @truncator.truncate_object(params)
131
-
132
- expect(params).to eq(bingo: Set.new(['[Circular]', :bango]))
133
- end
114
+ it "converts the object to a truncated string" do
115
+ expect(subject.length).to eq(max_len)
116
+ expect(subject).to eq('#<O[Truncated]')
134
117
  end
135
118
  end
136
119
 
137
- describe "given an array with a lot of elements" do
138
- context "the elements of which are also arrays with a lot of elements" do
139
- let(:params) do
140
- row = (0...4124).each_cons(2)
141
- { bingo: row.to_a.unshift(row.to_a) }
142
- end
143
-
144
- it "truncates all the arrays to the max allowed size" do
145
- expect(params[:bingo].size).to eq(4124)
146
- expect(params[:bingo][0].size).to eq(4123)
147
-
148
- @truncator.truncate_object(params)
149
-
150
- expect(params[:bingo].size).to eq(1000)
151
- expect(params[:bingo][0].size).to eq(1000)
152
- end
120
+ shared_examples 'self returning objects' do |object|
121
+ it "returns the passed object" do
122
+ expect(described_class.new(max_size).truncate(object)).to eql(object)
153
123
  end
154
124
  end
155
125
 
156
- describe "given a hash with long values" do
157
- context "which are strings" do
158
- let(:params) do
159
- { bingo: 'bango' * 2000, bongo: 'bish', bash: 'bosh' * 1000 }
160
- end
161
-
162
- it "truncates only long strings" do
163
- expect(params[:bingo].length).to eq(10_000)
164
- expect(params[:bongo].length).to eq(4)
165
- expect(params[:bash].length).to eq(4000)
166
-
167
- @truncator.truncate_object(params)
126
+ [1, true, false, :symbol, nil].each do |object|
127
+ include_examples 'self returning objects', object
128
+ end
168
129
 
169
- expect(params[:bingo].length).to eq(max_len)
170
- expect(params[:bongo].length).to eq(4)
171
- expect(params[:bash].length).to eq(max_len)
172
- end
130
+ context "given a recursive array" do
131
+ let(:object) do
132
+ a = %w[aaaaa bb]
133
+ a << a
134
+ a << 'c'
135
+ a
173
136
  end
174
137
 
175
- context "which are arrays" do
176
- context "of long strings" do
177
- let(:params) do
178
- { bingo: ['foo', 'bango' * 2000, 'bar', 'piyo' * 2000, 'baz'],
179
- bongo: 'bish',
180
- bash: 'bosh' * 1000 }
181
- end
182
-
183
- it "truncates long strings in the array, but not short ones" do
184
- expect(params[:bingo].map(&:length)).to eq([3, 10_000, 3, 8_000, 3])
185
- expect(params[:bongo].length).to eq(4)
186
- expect(params[:bash].length).to eq(4000)
187
-
188
- @truncator.truncate_object(params)
189
-
190
- expect(params[:bingo].map(&:length)).to eq([3, max_len, 3, max_len, 3])
191
- expect(params[:bongo].length).to eq(4)
192
- expect(params[:bash].length).to eq(max_len)
193
- end
194
- end
195
-
196
- context "of short strings" do
197
- let(:params) do
198
- { bingo: %w[foo bar baz], bango: 'bongo', bish: 'bash' }
199
- end
200
-
201
- it "truncates long strings in the array, but not short ones" do
202
- @truncator.truncate_object(params)
203
- expect(params).
204
- to eq(bingo: %w[foo bar baz], bango: 'bongo', bish: 'bash')
205
- end
206
- end
207
-
208
- context "of hashes" do
209
- context "with long strings" do
210
- let(:params) do
211
- { bingo: [{}, { bango: 'bongo', hoge: { fuga: 'piyo' * 2000 } }],
212
- bish: 'bash',
213
- bosh: 'foo' }
214
- end
215
-
216
- it "truncates the long string" do
217
- expect(params[:bingo][1][:hoge][:fuga].length).to eq(8000)
218
-
219
- @truncator.truncate_object(params)
220
-
221
- expect(params[:bingo][0]).to eq({})
222
- expect(params[:bingo][1][:bango]).to eq('bongo')
223
- expect(params[:bingo][1][:hoge][:fuga].length).to eq(max_len)
224
- expect(params[:bish]).to eq('bash')
225
- expect(params[:bosh]).to eq('foo')
226
- end
227
- end
228
-
229
- context "with short strings" do
230
- let(:params) do
231
- { bingo: [{}, { bango: 'bongo', hoge: { fuga: 'piyo' } }],
232
- bish: 'bash',
233
- bosh: 'foo' }
234
- end
235
-
236
- it "doesn't truncate the short string" do
237
- expect(params[:bingo][1][:hoge][:fuga].length).to eq(4)
238
-
239
- @truncator.truncate_object(params)
240
-
241
- expect(params[:bingo][0]).to eq({})
242
- expect(params[:bingo][1][:bango]).to eq('bongo')
243
- expect(params[:bingo][1][:hoge][:fuga].length).to eq(4)
244
- expect(params[:bish]).to eq('bash')
245
- expect(params[:bosh]).to eq('foo')
246
- end
247
- end
248
-
249
- context "with strings that equal to max_size" do
250
- before do
251
- @truncator = described_class.new(max_size)
252
- end
253
-
254
- let(:params) { { unicode: '1111' } }
255
- let(:max_size) { params[:unicode].size }
256
-
257
- it "is doesn't truncate the string" do
258
- @truncator.truncate_object(params)
259
-
260
- expect(params[:unicode].length).to eq(max_size)
261
- expect(params[:unicode]).to match(/\A1{#{max_size}}\z/)
262
- end
263
- end
264
- end
265
-
266
- context "of recursive hashes" do
267
- let(:params) do
268
- a = { bingo: {} }
269
- a[:bingo][:bango] = a
270
- end
271
-
272
- it "prevents recursion" do
273
- @truncator.truncate_object(params)
274
-
275
- expect(params).to eq(bingo: { bango: '[Circular]' })
276
- end
277
- end
278
-
279
- context "of arrays" do
280
- context "with long strings" do
281
- let(:params) do
282
- { bingo: ['bango', ['bongo', ['bish' * 2000]]],
283
- bish: 'bash',
284
- bosh: 'foo' }
285
- end
286
-
287
- it "truncates only the long string" do
288
- expect(params[:bingo][1][1][0].length).to eq(8000)
289
-
290
- @truncator.truncate_object(params)
291
-
292
- expect(params[:bingo][1][1][0].length).to eq(max_len)
293
- end
294
- end
295
- end
296
-
297
- context "of recursive arrays" do
298
- let(:params) do
299
- a = []
300
- a << a << :bango
301
- { bingo: a }
302
- end
303
-
304
- it "prevents recursion" do
305
- @truncator.truncate_object(params)
306
-
307
- expect(params).to eq(bingo: ['[Circular]', :bango])
308
- end
309
- end
138
+ it "prevents recursion" do
139
+ expect(subject).to eq(['aaa[Truncated]', 'bb', '[Circular]'])
310
140
  end
141
+ end
311
142
 
312
- context "which are arbitrary objects" do
313
- context "with default #to_s" do
314
- let(:params) { { bingo: Object.new } }
315
-
316
- it "converts the object to a safe string" do
317
- @truncator.truncate_object(params)
318
-
319
- expect(params[:bingo]).to include('Object')
320
- end
321
- end
322
-
323
- context "with redefined #to_s" do
324
- let(:params) do
325
- obj = Object.new
326
-
327
- def obj.to_s
328
- 'bango' * 2000
329
- end
330
-
331
- { bingo: obj }
332
- end
333
-
334
- it "truncates the string if it's too long" do
335
- @truncator.truncate_object(params)
336
-
337
- expect(params[:bingo].length).to eq(max_len)
338
- end
339
- end
340
-
341
- context "with other owner than Kernel" do
342
- let(:params) do
343
- mod = Module.new do
344
- def to_s
345
- "I am a fancy object" * 2000
346
- end
347
- end
348
-
349
- klass = Class.new { include mod }
350
-
351
- { bingo: klass.new }
352
- end
143
+ context "given a recursive array with recursive hashes" do
144
+ let(:object) do
145
+ a = []
146
+ a << a
353
147
 
354
- it "truncates the string it if it's long" do
355
- @truncator.truncate_object(params)
148
+ h = {}
149
+ h[:k] = h
150
+ a << h << 'aaaa'
151
+ end
356
152
 
357
- expect(params[:bingo].length).to eq(max_len)
358
- end
359
- end
153
+ it "prevents recursion" do
154
+ expect(subject).to eq(['[Circular]', { k: '[Circular]' }, 'aaa[Truncated]'])
155
+ expect(subject).to be_frozen
360
156
  end
157
+ end
361
158
 
362
- context "multiple copies of the same object" do
363
- let(:params) do
364
- bingo = []
365
- bango = ['bongo']
366
- bingo << bango << bango
367
- { bish: bingo }
368
- end
159
+ context "given a recursive set with recursive arrays" do
160
+ let(:object) do
161
+ s = Set.new
162
+ s << s
369
163
 
370
- it "are not being truncated" do
371
- @truncator.truncate_object(params)
164
+ h = {}
165
+ h[:k] = h
166
+ s << h << 'aaaa'
167
+ end
372
168
 
373
- expect(params).to eq(bish: [['bongo'], ['bongo']])
374
- end
169
+ it "prevents recursion" do
170
+ expect(subject).to eq(
171
+ Set.new(['[Circular]', { k: '[Circular]' }, 'aaa[Truncated]'])
172
+ )
173
+ expect(subject).to be_frozen
375
174
  end
376
175
  end
377
176
 
378
- describe "unicode payload" do
379
- before do
380
- @truncator = described_class.new(max_size - 1)
177
+ context "given a hash with long strings" do
178
+ let(:object) do
179
+ {
180
+ a: multiply_by_2_max_len('a'),
181
+ b: multiply_by_2_max_len('b'),
182
+ c: { d: multiply_by_2_max_len('d'), e: 'e' }
183
+ }
381
184
  end
382
185
 
383
- describe "truncation" do
384
- let(:params) { { unicode: "€€€€" } }
385
- let(:max_size) { params[:unicode].length }
386
-
387
- it "is performed correctly" do
388
- @truncator.truncate_object(params)
389
-
390
- expect(params[:unicode].length).to eq(max_len - 1)
391
- expect(params[:unicode]).to match(/\A€{#{max_size - 1}}\[Truncated\]\z/)
392
- end
186
+ it "truncates the long strings" do
187
+ expect(subject).to eq(
188
+ a: 'aaa[Truncated]', b: 'bbb[Truncated]', c: { d: 'ddd[Truncated]', e: 'e' }
189
+ )
190
+ expect(subject).to be_frozen
393
191
  end
192
+ end
394
193
 
395
- describe "string encoding conversion" do
396
- let(:params) { { unicode: "bad string€\xAE" } }
397
- let(:max_size) { 100 }
398
-
399
- it "converts strings to valid UTF-8" do
400
- @truncator.truncate_object(params)
401
-
402
- expect(params[:unicode]).to match(/\Abad string€[�\?]\z/)
403
- expect { params.to_json }.not_to raise_error
404
- end
405
-
406
- it "converts ASCII-8BIT strings with invalid characters to UTF-8 correctly" do
407
- # Shenanigans to get a bad ASCII-8BIT string. Direct conversion raises error.
408
- encoded = Base64.encode64("\xD3\xE6\xBC\x9D\xBA").encode!('ASCII-8BIT')
409
- bad_string = Base64.decode64(encoded)
410
-
411
- params = { unicode: bad_string }
412
-
413
- @truncator.truncate_object(params)
414
-
415
- expect(params[:unicode]).to match(/[�\?]{4}/)
416
- end
417
-
418
- it "doesn't fail when string is frozen" do
419
- encoded = Base64.encode64("\xD3\xE6\xBC\x9D\xBA").encode!('ASCII-8BIT')
420
- bad_string = Base64.decode64(encoded).freeze
421
-
422
- params = { unicode: bad_string }
423
-
424
- @truncator.truncate_object(params)
194
+ context "given a string with valid unicode characters" do
195
+ let(:object) { "€€€€€" }
425
196
 
426
- expect(params[:unicode]).to match(/[�\?]{4}/)
427
- end
197
+ it "truncates the string" do
198
+ expect(subject).to eq("€€€[Truncated]")
428
199
  end
429
200
  end
430
201
 
431
- describe "given a non-recursible object" do
432
- it "raises error" do
433
- expect { @truncator.truncate_object(:bingo) }.
434
- to raise_error(Airbrake::Error, /cannot truncate object/)
202
+ context "given an ASCII-8BIT string with invalid characters" do
203
+ let(:object) do
204
+ # Shenanigans to get a bad ASCII-8BIT string. Direct conversion raises error.
205
+ encoded = Base64.encode64("\xD3\xE6\xBC\x9D\xBA").encode!('ASCII-8BIT')
206
+ Base64.decode64(encoded).freeze
207
+ end
208
+
209
+ it "converts and truncates the string to UTF-8" do
210
+ expect(subject).to eq("���[Truncated]")
211
+ expect(subject).to be_frozen
435
212
  end
436
213
  end
437
214
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: airbrake-ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.5.1
4
+ version: 2.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Airbrake Technologies, Inc.
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-10-26 00:00:00.000000000 Z
11
+ date: 2017-11-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rspec