airbrake-ruby 2.5.1 → 2.6.0

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