airbrake-ruby 4.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. checksums.yaml +7 -0
  2. data/lib/airbrake-ruby.rb +515 -0
  3. data/lib/airbrake-ruby/async_sender.rb +80 -0
  4. data/lib/airbrake-ruby/backtrace.rb +196 -0
  5. data/lib/airbrake-ruby/benchmark.rb +39 -0
  6. data/lib/airbrake-ruby/code_hunk.rb +51 -0
  7. data/lib/airbrake-ruby/config.rb +229 -0
  8. data/lib/airbrake-ruby/config/validator.rb +91 -0
  9. data/lib/airbrake-ruby/deploy_notifier.rb +36 -0
  10. data/lib/airbrake-ruby/file_cache.rb +54 -0
  11. data/lib/airbrake-ruby/filter_chain.rb +95 -0
  12. data/lib/airbrake-ruby/filters/context_filter.rb +29 -0
  13. data/lib/airbrake-ruby/filters/dependency_filter.rb +31 -0
  14. data/lib/airbrake-ruby/filters/exception_attributes_filter.rb +46 -0
  15. data/lib/airbrake-ruby/filters/gem_root_filter.rb +33 -0
  16. data/lib/airbrake-ruby/filters/git_last_checkout_filter.rb +92 -0
  17. data/lib/airbrake-ruby/filters/git_repository_filter.rb +64 -0
  18. data/lib/airbrake-ruby/filters/git_revision_filter.rb +66 -0
  19. data/lib/airbrake-ruby/filters/keys_blacklist.rb +49 -0
  20. data/lib/airbrake-ruby/filters/keys_filter.rb +140 -0
  21. data/lib/airbrake-ruby/filters/keys_whitelist.rb +48 -0
  22. data/lib/airbrake-ruby/filters/root_directory_filter.rb +28 -0
  23. data/lib/airbrake-ruby/filters/sql_filter.rb +125 -0
  24. data/lib/airbrake-ruby/filters/system_exit_filter.rb +23 -0
  25. data/lib/airbrake-ruby/filters/thread_filter.rb +92 -0
  26. data/lib/airbrake-ruby/hash_keyable.rb +37 -0
  27. data/lib/airbrake-ruby/ignorable.rb +44 -0
  28. data/lib/airbrake-ruby/inspectable.rb +39 -0
  29. data/lib/airbrake-ruby/loggable.rb +34 -0
  30. data/lib/airbrake-ruby/monotonic_time.rb +43 -0
  31. data/lib/airbrake-ruby/nested_exception.rb +38 -0
  32. data/lib/airbrake-ruby/notice.rb +162 -0
  33. data/lib/airbrake-ruby/notice_notifier.rb +134 -0
  34. data/lib/airbrake-ruby/performance_breakdown.rb +46 -0
  35. data/lib/airbrake-ruby/performance_notifier.rb +155 -0
  36. data/lib/airbrake-ruby/promise.rb +109 -0
  37. data/lib/airbrake-ruby/query.rb +54 -0
  38. data/lib/airbrake-ruby/request.rb +46 -0
  39. data/lib/airbrake-ruby/response.rb +74 -0
  40. data/lib/airbrake-ruby/stashable.rb +15 -0
  41. data/lib/airbrake-ruby/stat.rb +73 -0
  42. data/lib/airbrake-ruby/sync_sender.rb +113 -0
  43. data/lib/airbrake-ruby/tdigest.rb +393 -0
  44. data/lib/airbrake-ruby/thread_pool.rb +128 -0
  45. data/lib/airbrake-ruby/time_truncate.rb +17 -0
  46. data/lib/airbrake-ruby/timed_trace.rb +58 -0
  47. data/lib/airbrake-ruby/truncator.rb +115 -0
  48. data/lib/airbrake-ruby/version.rb +6 -0
  49. data/spec/airbrake_spec.rb +324 -0
  50. data/spec/async_sender_spec.rb +72 -0
  51. data/spec/backtrace_spec.rb +427 -0
  52. data/spec/benchmark_spec.rb +33 -0
  53. data/spec/code_hunk_spec.rb +115 -0
  54. data/spec/config/validator_spec.rb +184 -0
  55. data/spec/config_spec.rb +154 -0
  56. data/spec/deploy_notifier_spec.rb +48 -0
  57. data/spec/file_cache_spec.rb +34 -0
  58. data/spec/filter_chain_spec.rb +92 -0
  59. data/spec/filters/context_filter_spec.rb +23 -0
  60. data/spec/filters/dependency_filter_spec.rb +12 -0
  61. data/spec/filters/exception_attributes_filter_spec.rb +50 -0
  62. data/spec/filters/gem_root_filter_spec.rb +41 -0
  63. data/spec/filters/git_last_checkout_filter_spec.rb +46 -0
  64. data/spec/filters/git_repository_filter.rb +61 -0
  65. data/spec/filters/git_revision_filter_spec.rb +126 -0
  66. data/spec/filters/keys_blacklist_spec.rb +225 -0
  67. data/spec/filters/keys_whitelist_spec.rb +194 -0
  68. data/spec/filters/root_directory_filter_spec.rb +39 -0
  69. data/spec/filters/sql_filter_spec.rb +262 -0
  70. data/spec/filters/system_exit_filter_spec.rb +14 -0
  71. data/spec/filters/thread_filter_spec.rb +277 -0
  72. data/spec/fixtures/notroot.txt +7 -0
  73. data/spec/fixtures/project_root/code.rb +221 -0
  74. data/spec/fixtures/project_root/empty_file.rb +0 -0
  75. data/spec/fixtures/project_root/long_line.txt +1 -0
  76. data/spec/fixtures/project_root/short_file.rb +3 -0
  77. data/spec/fixtures/project_root/vendor/bundle/ignored_file.rb +5 -0
  78. data/spec/helpers.rb +9 -0
  79. data/spec/ignorable_spec.rb +14 -0
  80. data/spec/inspectable_spec.rb +45 -0
  81. data/spec/monotonic_time_spec.rb +12 -0
  82. data/spec/nested_exception_spec.rb +73 -0
  83. data/spec/notice_notifier/options_spec.rb +259 -0
  84. data/spec/notice_notifier_spec.rb +356 -0
  85. data/spec/notice_spec.rb +296 -0
  86. data/spec/performance_breakdown_spec.rb +12 -0
  87. data/spec/performance_notifier_spec.rb +491 -0
  88. data/spec/promise_spec.rb +197 -0
  89. data/spec/query_spec.rb +11 -0
  90. data/spec/request_spec.rb +11 -0
  91. data/spec/response_spec.rb +88 -0
  92. data/spec/spec_helper.rb +100 -0
  93. data/spec/stashable_spec.rb +23 -0
  94. data/spec/stat_spec.rb +47 -0
  95. data/spec/sync_sender_spec.rb +133 -0
  96. data/spec/tdigest_spec.rb +230 -0
  97. data/spec/thread_pool_spec.rb +158 -0
  98. data/spec/time_truncate_spec.rb +13 -0
  99. data/spec/timed_trace_spec.rb +125 -0
  100. data/spec/truncator_spec.rb +238 -0
  101. metadata +216 -0
@@ -0,0 +1,197 @@
1
+ RSpec.describe Airbrake::Promise do
2
+ describe ".then" do
3
+ let(:resolved_with) { [] }
4
+ let(:rejected_with) { [] }
5
+
6
+ context "when it is not resolved" do
7
+ it "returns self" do
8
+ expect(subject.then {}).to eq(subject)
9
+ end
10
+
11
+ it "doesn't call the resolve callbacks yet" do
12
+ subject.then { resolved_with << 1 }.then { resolved_with << 2 }
13
+ expect(resolved_with).to be_empty
14
+ end
15
+ end
16
+
17
+ context "when it is resolved" do
18
+ shared_examples "then specs" do
19
+ it "returns self" do
20
+ expect(subject.then {}).to eq(subject)
21
+ end
22
+
23
+ it "yields the resolved value" do
24
+ yielded = nil
25
+ subject.then { |value| yielded = value }
26
+ expect(yielded).to eq('id' => '123')
27
+ end
28
+
29
+ it "calls the resolve callbacks" do
30
+ expect(resolved_with).to match_array([1, 2])
31
+ end
32
+
33
+ it "doesn't call the reject callbacks" do
34
+ expect(rejected_with).to be_empty
35
+ end
36
+ end
37
+
38
+ context "and there are some resolve and reject callbacks in place" do
39
+ before do
40
+ subject.then { resolved_with << 1 }.then { resolved_with << 2 }
41
+ subject.rescue { rejected_with << 1 }.rescue { rejected_with << 2 }
42
+ subject.resolve('id' => '123')
43
+ end
44
+
45
+ include_examples "then specs"
46
+
47
+ it "registers the resolve callbacks" do
48
+ subject.resolve('id' => '456')
49
+ expect(resolved_with).to match_array([1, 2, 1, 2])
50
+ end
51
+ end
52
+
53
+ context "and additional then callbacks are added" do
54
+ before do
55
+ subject.resolve('id' => '123')
56
+ subject.then { resolved_with << 1 }.then { resolved_with << 2 }
57
+ end
58
+
59
+ include_examples "then specs"
60
+
61
+ it "doesn't register new resolve callbacks" do
62
+ subject.resolve('id' => '456')
63
+ expect(resolved_with).to match_array([1, 2])
64
+ end
65
+ end
66
+ end
67
+ end
68
+
69
+ describe ".rescue" do
70
+ let(:resolved_with) { [] }
71
+ let(:rejected_with) { [] }
72
+
73
+ context "when it is not rejected" do
74
+ it "returns self" do
75
+ expect(subject.then {}).to eq(subject)
76
+ end
77
+
78
+ it "doesn't call the reject callbacks yet" do
79
+ subject.rescue { rejected_with << 1 }.rescue { rejected_with << 2 }
80
+ expect(rejected_with).to be_empty
81
+ end
82
+ end
83
+
84
+ context "when it is rejected" do
85
+ shared_examples "rescue specs" do
86
+ it "returns self" do
87
+ expect(subject.rescue {}).to eq(subject)
88
+ end
89
+
90
+ it "yields the rejected value" do
91
+ yielded = nil
92
+ subject.rescue { |value| yielded = value }
93
+ expect(yielded).to eq('bingo')
94
+ end
95
+
96
+ it "doesn't call the resolve callbacks" do
97
+ expect(resolved_with).to be_empty
98
+ end
99
+
100
+ it "calls the reject callbacks" do
101
+ expect(rejected_with).to match_array([1, 2])
102
+ end
103
+ end
104
+
105
+ context "and there are some resolve and reject callbacks in place" do
106
+ before do
107
+ subject.then { resolved_with << 1 }.then { resolved_with << 2 }
108
+ subject.rescue { rejected_with << 1 }.rescue { rejected_with << 2 }
109
+ subject.reject('bingo')
110
+ end
111
+
112
+ include_examples "rescue specs"
113
+
114
+ it "registers the reject callbacks" do
115
+ subject.reject('bingo again')
116
+ expect(rejected_with).to match_array([1, 2, 1, 2])
117
+ end
118
+ end
119
+
120
+ context "and additional reject callbacks are added" do
121
+ before do
122
+ subject.reject('bingo')
123
+ subject.rescue { rejected_with << 1 }.rescue { rejected_with << 2 }
124
+ end
125
+
126
+ include_examples "rescue specs"
127
+
128
+ it "doesn't register new reject callbacks" do
129
+ subject.reject('bingo again')
130
+ expect(rejected_with).to match_array([1, 2])
131
+ end
132
+ end
133
+ end
134
+ end
135
+
136
+ describe ".resolve" do
137
+ it "returns self" do
138
+ expect(subject.resolve(1)).to eq(subject)
139
+ end
140
+
141
+ it "executes callbacks attached with .then" do
142
+ array = []
143
+ subject.then { |notice_id| array << notice_id }.rescue { array << 999 }
144
+
145
+ expect(array.size).to be_zero
146
+ subject.resolve(1)
147
+ expect(array).to match_array([1])
148
+ end
149
+ end
150
+
151
+ describe ".reject" do
152
+ it "returns self" do
153
+ expect(subject.reject(1)).to eq(subject)
154
+ end
155
+
156
+ it "executes callbacks attached with .rescue" do
157
+ array = []
158
+ subject.then { array << 1 }.rescue { |error| array << error }
159
+
160
+ expect(array.size).to be_zero
161
+ subject.reject(999)
162
+ expect(array).to match_array([999])
163
+ end
164
+ end
165
+
166
+ describe "#rejected?" do
167
+ context "when it was rejected" do
168
+ before { subject.reject(1) }
169
+ it { is_expected.to be_rejected }
170
+ end
171
+
172
+ context "when it wasn't rejected" do
173
+ it { is_expected.not_to be_rejected }
174
+ end
175
+
176
+ context "when it was resolved" do
177
+ before { subject.resolve }
178
+ it { is_expected.not_to be_rejected }
179
+ end
180
+ end
181
+
182
+ describe "#resolved?" do
183
+ context "when it was resolved" do
184
+ before { subject.resolve }
185
+ it { is_expected.to be_resolved }
186
+ end
187
+
188
+ context "when it wasn't resolved" do
189
+ it { is_expected.not_to be_resolved }
190
+ end
191
+
192
+ context "when it was rejected" do
193
+ before { subject.reject(1) }
194
+ it { is_expected.not_to be_resolved }
195
+ end
196
+ end
197
+ end
@@ -0,0 +1,11 @@
1
+ RSpec.describe Airbrake::Query do
2
+ describe "#stash" do
3
+ subject do
4
+ described_class.new(
5
+ method: 'GET', route: '/', query: '', start_time: Time.now
6
+ )
7
+ end
8
+
9
+ it { is_expected.to respond_to(:stash) }
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ RSpec.describe Airbrake::Request do
2
+ describe "#stash" do
3
+ subject do
4
+ described_class.new(
5
+ method: 'GET', route: '/', status_code: 200, start_time: Time.now
6
+ )
7
+ end
8
+
9
+ it { is_expected.to respond_to(:stash) }
10
+ end
11
+ end
@@ -0,0 +1,88 @@
1
+ RSpec.describe Airbrake::Response do
2
+ describe ".parse" do
3
+ [200, 201, 204].each do |code|
4
+ context "when response code is #{code}" do
5
+ it "logs response body" do
6
+ expect(Airbrake::Loggable.instance).to receive(:debug).with(
7
+ /Airbrake::Response \(#{code}\): {}/
8
+ )
9
+ described_class.parse(OpenStruct.new(code: code, body: '{}'))
10
+ end
11
+ end
12
+ end
13
+
14
+ [400, 401, 403, 420].each do |code|
15
+ context "when response code is #{code}" do
16
+ it "logs response message" do
17
+ expect(Airbrake::Loggable.instance).to receive(:error).with(
18
+ /Airbrake: foo/
19
+ )
20
+ described_class.parse(
21
+ OpenStruct.new(code: code, body: '{"message":"foo"}')
22
+ )
23
+ end
24
+ end
25
+ end
26
+
27
+ context "when response code is 429" do
28
+ let(:response) { OpenStruct.new(code: 429, body: '{"message":"rate limited"}') }
29
+
30
+ it "logs response message" do
31
+ expect(Airbrake::Loggable.instance).to receive(:error).with(
32
+ /Airbrake: rate limited/
33
+ )
34
+ described_class.parse(response)
35
+ end
36
+
37
+ it "returns an error response" do
38
+ time = Time.now
39
+ allow(Time).to receive(:now).and_return(time)
40
+
41
+ resp = described_class.parse(response)
42
+ expect(resp).to include(
43
+ 'error' => '**Airbrake: rate limited',
44
+ 'rate_limit_reset' => time
45
+ )
46
+ end
47
+ end
48
+
49
+ context "when response code is unhandled" do
50
+ let(:response) { OpenStruct.new(code: 500, body: 'foo') }
51
+
52
+ it "logs response body" do
53
+ expect(Airbrake::Loggable.instance).to receive(:error).with(
54
+ /Airbrake: unexpected code \(500\)\. Body: foo/
55
+ )
56
+ described_class.parse(response)
57
+ end
58
+
59
+ it "returns an error response" do
60
+ resp = described_class.parse(response)
61
+ expect(resp).to eq('error' => 'foo')
62
+ end
63
+
64
+ it "truncates body" do
65
+ response.body *= 1000
66
+ resp = described_class.parse(response)
67
+ expect(resp).to eq('error' => ('foo' * 33) + 'fo...')
68
+ end
69
+ end
70
+
71
+ context "when response body can't be parsed as JSON" do
72
+ let(:response) { OpenStruct.new(code: 201, body: 'foo') }
73
+
74
+ it "logs response body" do
75
+ expect(Airbrake::Loggable.instance).to receive(:error).with(
76
+ /Airbrake: error while parsing body \(.*unexpected token.*\)\. Body: foo/
77
+ )
78
+ described_class.parse(response)
79
+ end
80
+
81
+ it "returns an error message" do
82
+ expect(described_class.parse(response)['error']).to match(
83
+ /\A#<JSON::ParserError.+>/
84
+ )
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,100 @@
1
+ require 'simplecov'
2
+ SimpleCov.start if ENV['COVERAGE']
3
+
4
+ require 'airbrake-ruby'
5
+
6
+ require 'rspec/its'
7
+
8
+ require 'webmock'
9
+ require 'webmock/rspec'
10
+ require 'pry'
11
+
12
+ require 'pathname'
13
+ require 'webrick'
14
+ require 'English'
15
+ require 'base64'
16
+ require 'pp'
17
+
18
+ require 'helpers'
19
+
20
+ RSpec.configure do |c|
21
+ c.order = 'random'
22
+ c.color = true
23
+ c.disable_monkey_patching!
24
+ c.include Helpers
25
+ end
26
+
27
+ Thread.abort_on_exception = true
28
+
29
+ WebMock.disable_net_connect!(allow_localhost: true)
30
+
31
+ class AirbrakeTestError < RuntimeError
32
+ attr_reader :backtrace
33
+
34
+ def initialize(*)
35
+ super
36
+ # rubocop:disable Metrics/LineLength
37
+ @backtrace = [
38
+ "/home/kyrylo/code/airbrake/ruby/spec/spec_helper.rb:23:in `<top (required)>'",
39
+ "/opt/rubies/ruby-2.2.2/lib/ruby/2.2.0/rubygems/core_ext/kernel_require.rb:54:in `require'",
40
+ "/opt/rubies/ruby-2.2.2/lib/ruby/2.2.0/rubygems/core_ext/kernel_require.rb:54:in `require'",
41
+ "/home/kyrylo/code/airbrake/ruby/spec/airbrake_spec.rb:1:in `<top (required)>'",
42
+ "/home/kyrylo/.gem/ruby/2.2.2/gems/rspec-core-3.3.2/lib/rspec/core/configuration.rb:1327:in `load'",
43
+ "/home/kyrylo/.gem/ruby/2.2.2/gems/rspec-core-3.3.2/lib/rspec/core/configuration.rb:1327:in `block in load_spec_files'",
44
+ "/home/kyrylo/.gem/ruby/2.2.2/gems/rspec-core-3.3.2/lib/rspec/core/configuration.rb:1325:in `each'",
45
+ "/home/kyrylo/.gem/ruby/2.2.2/gems/rspec-core-3.3.2/lib/rspec/core/configuration.rb:1325:in `load_spec_files'",
46
+ "/home/kyrylo/.gem/ruby/2.2.2/gems/rspec-core-3.3.2/lib/rspec/core/runner.rb:102:in `setup'",
47
+ "/home/kyrylo/.gem/ruby/2.2.2/gems/rspec-core-3.3.2/lib/rspec/core/runner.rb:88:in `run'",
48
+ "/home/kyrylo/.gem/ruby/2.2.2/gems/rspec-core-3.3.2/lib/rspec/core/runner.rb:73:in `run'",
49
+ "/home/kyrylo/.gem/ruby/2.2.2/gems/rspec-core-3.3.2/lib/rspec/core/runner.rb:41:in `invoke'",
50
+ "/home/kyrylo/.gem/ruby/2.2.2/gems/rspec-core-3.3.2/exe/rspec:4:in `<main>'"
51
+ ]
52
+ # rubocop:enable Metrics/LineLength
53
+ end
54
+
55
+ # rubocop:disable Naming/AccessorMethodName
56
+ def set_backtrace(backtrace)
57
+ @backtrace = backtrace
58
+ end
59
+ # rubocop:enable Naming/AccessorMethodName
60
+
61
+ def message
62
+ 'App crashed!'
63
+ end
64
+ end
65
+
66
+ class JavaAirbrakeTestError < AirbrakeTestError
67
+ def initialize(*)
68
+ super
69
+ # rubocop:disable Metrics/LineLength
70
+ @backtrace = [
71
+ "org.jruby.java.invokers.InstanceMethodInvoker.call(InstanceMethodInvoker.java:26)",
72
+ "org.jruby.ir.interpreter.Interpreter.INTERPRET_EVAL(Interpreter.java:126)",
73
+ "org.jruby.RubyKernel$INVOKER$s$0$3$eval19.call(RubyKernel$INVOKER$s$0$3$eval19.gen)",
74
+ "org.jruby.RubyKernel$INVOKER$s$0$0$loop.call(RubyKernel$INVOKER$s$0$0$loop.gen)",
75
+ "org.jruby.runtime.IRBlockBody.doYield(IRBlockBody.java:139)",
76
+ "org.jruby.RubyKernel$INVOKER$s$rbCatch19.call(RubyKernel$INVOKER$s$rbCatch19.gen)",
77
+ "opt.rubies.jruby_minus_9_dot_0_dot_0_dot_0.bin.irb.invokeOther4:start(/opt/rubies/jruby-9.0.0.0/bin/irb)",
78
+ "opt.rubies.jruby_minus_9_dot_0_dot_0_dot_0.bin.irb.RUBY$script(/opt/rubies/jruby-9.0.0.0/bin/irb:13)",
79
+ "org.jruby.ir.Compiler$1.load(Compiler.java:111)",
80
+ "org.jruby.Main.run(Main.java:225)",
81
+ "org.jruby.Main.main(Main.java:197)"
82
+ ]
83
+ # rubocop:enable Metrics/LineLength
84
+ end
85
+
86
+ def is_a?(*)
87
+ true
88
+ end
89
+ end
90
+
91
+ class Ruby21Error < RuntimeError
92
+ attr_accessor :cause
93
+
94
+ def self.raise_error(msg)
95
+ ex = new(msg)
96
+ ex.cause = $ERROR_INFO
97
+
98
+ raise ex
99
+ end
100
+ end
@@ -0,0 +1,23 @@
1
+ RSpec.describe Airbrake::Stashable do
2
+ let(:klass) do
3
+ mod = described_class
4
+ Class.new { include(mod) }
5
+ end
6
+
7
+ describe "#stash" do
8
+ subject { klass.new }
9
+
10
+ it "returns a hash" do
11
+ expect(subject.stash).to be_a(Hash)
12
+ end
13
+
14
+ it "returns an empty hash" do
15
+ expect(subject.stash).to be_empty
16
+ end
17
+
18
+ it "remembers what was put in the stash" do
19
+ subject.stash[:foo] = 1
20
+ expect(subject.stash[:foo]).to eq(1)
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,47 @@
1
+ RSpec.describe Airbrake::Stat do
2
+ describe "#to_h" do
3
+ it "converts to a hash" do
4
+ expect(subject.to_h).to eq(
5
+ 'count' => 0,
6
+ 'sum' => 0.0,
7
+ 'sumsq' => 0.0,
8
+ 'tdigest' => 'AAAAAkA0AAAAAAAAAAAAAA=='
9
+ )
10
+ end
11
+ end
12
+
13
+ describe "#increment" do
14
+ let(:start_time) { Time.new(2018, 1, 1, 0, 0, 20, 0) }
15
+ let(:end_time) { Time.new(2018, 1, 1, 0, 0, 22, 0) }
16
+
17
+ before { subject.increment(start_time, end_time) }
18
+
19
+ its(:sum) { is_expected.to eq(2000) }
20
+ end
21
+
22
+ describe "#increment_ms" do
23
+ before { subject.increment_ms(1000) }
24
+
25
+ its(:count) { is_expected.to eq(1) }
26
+ its(:sum) { is_expected.to eq(1000) }
27
+ its(:sumsq) { is_expected.to eq(1000000) }
28
+
29
+ it "updates tdigest" do
30
+ expect(subject.tdigest.size).to eq(1)
31
+ end
32
+ end
33
+
34
+ describe "#inspect" do
35
+ it "provides custom inspect output" do
36
+ expect(subject.inspect).to eq(
37
+ '#<struct Airbrake::Stat count=0, sum=0.0, sumsq=0.0>'
38
+ )
39
+ end
40
+ end
41
+
42
+ describe "#pretty_print" do
43
+ it "is an alias of #inspect" do
44
+ expect(subject.method(:pretty_print)).to eql(subject.method(:inspect))
45
+ end
46
+ end
47
+ end