airbrake-ruby 4.7.0

Sign up to get free protection for your applications and to get access to all the features.
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,72 @@
1
+ RSpec.describe Airbrake::AsyncSender do
2
+ let(:endpoint) { 'https://api.airbrake.io/api/v3/projects/1/notices' }
3
+ let(:queue_size) { 10 }
4
+ let(:notice) { Airbrake::Notice.new(AirbrakeTestError.new) }
5
+
6
+ before do
7
+ stub_request(:post, endpoint).to_return(status: 201, body: '{}')
8
+ Airbrake::Config.instance = Airbrake::Config.new(
9
+ project_id: '1',
10
+ workers: 3,
11
+ queue_size: 10
12
+ )
13
+ end
14
+
15
+ describe "#send" do
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) }
19
+ subject.close
20
+
21
+ expect(a_request(:post, endpoint)).to have_been_made.twice
22
+ end
23
+
24
+ it "returns a resolved promise" do
25
+ promise = Airbrake::Promise.new
26
+ subject.send(notice, promise)
27
+ subject.close
28
+
29
+ expect(promise).to be_resolved
30
+ end
31
+ end
32
+
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
39
+ )
40
+ end
41
+
42
+ it "doesn't send the exceeded notices to Airbrake" do
43
+ 15.times { subject.send(notice, Airbrake::Promise.new) }
44
+ subject.close
45
+
46
+ expect(a_request(:post, endpoint)).not_to have_been_made
47
+ end
48
+
49
+ it "returns a rejected promise" do
50
+ promise = nil
51
+ 15.times do
52
+ promise = subject.send(notice, Airbrake::Promise.new)
53
+ end
54
+ subject.close
55
+
56
+ expect(promise).to be_rejected
57
+ expect(promise.value).to eq(
58
+ 'error' => "AsyncSender has reached its capacity of 1"
59
+ )
60
+ end
61
+
62
+ it "logs discarded notice" do
63
+ expect(Airbrake::Loggable.instance).to receive(:error).with(
64
+ /reached its capacity/
65
+ ).at_least(:once)
66
+
67
+ 15.times { subject.send(notice, Airbrake::Promise.new) }
68
+ subject.close
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,427 @@
1
+ RSpec.describe Airbrake::Backtrace do
2
+ describe ".parse" do
3
+ context "UNIX backtrace" do
4
+ let(:parsed_backtrace) do
5
+ # rubocop:disable Metrics/LineLength, Style/HashSyntax, Layout/SpaceAroundOperators, Layout/SpaceInsideHashLiteralBraces
6
+ [{:file=>"/home/kyrylo/code/airbrake/ruby/spec/spec_helper.rb", :line=>23, :function=>"<top (required)>"},
7
+ {:file=>"/opt/rubies/ruby-2.2.2/lib/ruby/2.2.0/rubygems/core_ext/kernel_require.rb", :line=>54, :function=>"require"},
8
+ {:file=>"/opt/rubies/ruby-2.2.2/lib/ruby/2.2.0/rubygems/core_ext/kernel_require.rb", :line=>54, :function=>"require"},
9
+ {:file=>"/home/kyrylo/code/airbrake/ruby/spec/airbrake_spec.rb", :line=>1, :function=>"<top (required)>"},
10
+ {:file=>"/home/kyrylo/.gem/ruby/2.2.2/gems/rspec-core-3.3.2/lib/rspec/core/configuration.rb", :line=>1327, :function=>"load"},
11
+ {:file=>"/home/kyrylo/.gem/ruby/2.2.2/gems/rspec-core-3.3.2/lib/rspec/core/configuration.rb", :line=>1327, :function=>"block in load_spec_files"},
12
+ {:file=>"/home/kyrylo/.gem/ruby/2.2.2/gems/rspec-core-3.3.2/lib/rspec/core/configuration.rb", :line=>1325, :function=>"each"},
13
+ {:file=>"/home/kyrylo/.gem/ruby/2.2.2/gems/rspec-core-3.3.2/lib/rspec/core/configuration.rb", :line=>1325, :function=>"load_spec_files"},
14
+ {:file=>"/home/kyrylo/.gem/ruby/2.2.2/gems/rspec-core-3.3.2/lib/rspec/core/runner.rb", :line=>102, :function=>"setup"},
15
+ {:file=>"/home/kyrylo/.gem/ruby/2.2.2/gems/rspec-core-3.3.2/lib/rspec/core/runner.rb", :line=>88, :function=>"run"},
16
+ {:file=>"/home/kyrylo/.gem/ruby/2.2.2/gems/rspec-core-3.3.2/lib/rspec/core/runner.rb", :line=>73, :function=>"run"},
17
+ {:file=>"/home/kyrylo/.gem/ruby/2.2.2/gems/rspec-core-3.3.2/lib/rspec/core/runner.rb", :line=>41, :function=>"invoke"},
18
+ {:file=>"/home/kyrylo/.gem/ruby/2.2.2/gems/rspec-core-3.3.2/exe/rspec", :line=>4, :function=>"<main>"}]
19
+ # rubocop:enable Metrics/LineLength, Style/HashSyntax, Layout/SpaceAroundOperators, Layout/SpaceInsideHashLiteralBraces
20
+ end
21
+
22
+ it "returns a properly formatted array of hashes" do
23
+ expect(described_class.parse(AirbrakeTestError.new))
24
+ .to eq(parsed_backtrace)
25
+ end
26
+ end
27
+
28
+ context "Windows backtrace" do
29
+ let(:windows_bt) do
30
+ ["C:/Program Files/Server/app/models/user.rb:13:in `magic'",
31
+ "C:/Program Files/Server/app/controllers/users_controller.rb:8:in `index'"]
32
+ end
33
+
34
+ let(:ex) { AirbrakeTestError.new.tap { |e| e.set_backtrace(windows_bt) } }
35
+
36
+ let(:parsed_backtrace) do
37
+ # rubocop:disable Metrics/LineLength, Style/HashSyntax, Layout/SpaceInsideHashLiteralBraces, Layout/SpaceAroundOperators
38
+ [{:file=>"C:/Program Files/Server/app/models/user.rb", :line=>13, :function=>"magic"},
39
+ {:file=>"C:/Program Files/Server/app/controllers/users_controller.rb", :line=>8, :function=>"index"}]
40
+ # rubocop:enable Metrics/LineLength, Style/HashSyntax, Layout/SpaceInsideHashLiteralBraces, Layout/SpaceAroundOperators
41
+ end
42
+
43
+ it "returns a properly formatted array of hashes" do
44
+ expect(described_class.parse(ex)).to eq(parsed_backtrace)
45
+ end
46
+ end
47
+
48
+ context "JRuby Java exceptions" do
49
+ let(:backtrace_array) do
50
+ # rubocop:disable Metrics/LineLength, Style/HashSyntax, Layout/SpaceInsideHashLiteralBraces, Layout/SpaceAroundOperators
51
+ [{:file=>"InstanceMethodInvoker.java", :line=>26, :function=>"org.jruby.java.invokers.InstanceMethodInvoker.call"},
52
+ {:file=>"Interpreter.java", :line=>126, :function=>"org.jruby.ir.interpreter.Interpreter.INTERPRET_EVAL"},
53
+ {:file=>"RubyKernel$INVOKER$s$0$3$eval19.gen", :line=>nil, :function=>"org.jruby.RubyKernel$INVOKER$s$0$3$eval19.call"},
54
+ {:file=>"RubyKernel$INVOKER$s$0$0$loop.gen", :line=>nil, :function=>"org.jruby.RubyKernel$INVOKER$s$0$0$loop.call"},
55
+ {:file=>"IRBlockBody.java", :line=>139, :function=>"org.jruby.runtime.IRBlockBody.doYield"},
56
+ {:file=>"RubyKernel$INVOKER$s$rbCatch19.gen", :line=>nil, :function=>"org.jruby.RubyKernel$INVOKER$s$rbCatch19.call"},
57
+ {:file=>"/opt/rubies/jruby-9.0.0.0/bin/irb", :line=>nil, :function=>"opt.rubies.jruby_minus_9_dot_0_dot_0_dot_0.bin.irb.invokeOther4:start"},
58
+ {:file=>"/opt/rubies/jruby-9.0.0.0/bin/irb", :line=>13, :function=>"opt.rubies.jruby_minus_9_dot_0_dot_0_dot_0.bin.irb.RUBY$script"},
59
+ {:file=>"Compiler.java", :line=>111, :function=>"org.jruby.ir.Compiler$1.load"},
60
+ {:file=>"Main.java", :line=>225, :function=>"org.jruby.Main.run"},
61
+ {:file=>"Main.java", :line=>197, :function=>"org.jruby.Main.main"}]
62
+ # rubocop:enable Metrics/LineLength, Style/HashSyntax, Layout/SpaceInsideHashLiteralBraces, Layout/SpaceAroundOperators
63
+ end
64
+
65
+ it "returns a properly formatted array of hashes" do
66
+ allow(described_class).to receive(:java_exception?).and_return(true)
67
+
68
+ expect(described_class.parse(JavaAirbrakeTestError.new))
69
+ .to eq(backtrace_array)
70
+ end
71
+ end
72
+
73
+ context "JRuby classloader exceptions" do
74
+ let(:backtrace) do
75
+ # rubocop:disable Metrics/LineLength
76
+ ['uri_3a_classloader_3a_.META_minus_INF.jruby_dot_home.lib.ruby.stdlib.net.protocol.rbuf_fill(uri:classloader:/META-INF/jruby.home/lib/ruby/stdlib/net/protocol.rb:158)',
77
+ 'bin.processors.image_uploader.block in make_streams(bin/processors/image_uploader.rb:21)',
78
+ 'uri_3a_classloader_3a_.gems.faye_minus_websocket_minus_0_dot_10_dot_5.lib.faye.websocket.api.invokeOther13:dispatch_event(uri_3a_classloader_3a_/gems/faye_minus_websocket_minus_0_dot_10_dot_5/lib/faye/websocket/uri:classloader:/gems/faye-websocket-0.10.5/lib/faye/websocket/api.rb:109)',
79
+ 'tmp.jruby9022301782566983632extract.$dot.META_minus_INF.rails.file(/tmp/jruby9022301782566983632extract/./META-INF/rails.rb:13)']
80
+ # rubocop:enable Metrics/LineLength
81
+ end
82
+
83
+ let(:parsed_backtrace) do
84
+ # rubocop:disable Metrics/LineLength
85
+ [{ file: 'uri:classloader:/META-INF/jruby.home/lib/ruby/stdlib/net/protocol.rb', line: 158, function: 'uri_3a_classloader_3a_.META_minus_INF.jruby_dot_home.lib.ruby.stdlib.net.protocol.rbuf_fill' },
86
+ { file: 'bin/processors/image_uploader.rb', line: 21, function: 'bin.processors.image_uploader.block in make_streams' },
87
+ { file: 'uri_3a_classloader_3a_/gems/faye_minus_websocket_minus_0_dot_10_dot_5/lib/faye/websocket/uri:classloader:/gems/faye-websocket-0.10.5/lib/faye/websocket/api.rb', line: 109, function: 'uri_3a_classloader_3a_.gems.faye_minus_websocket_minus_0_dot_10_dot_5.lib.faye.websocket.api.invokeOther13:dispatch_event' },
88
+ { file: '/tmp/jruby9022301782566983632extract/./META-INF/rails.rb', line: 13, function: 'tmp.jruby9022301782566983632extract.$dot.META_minus_INF.rails.file' }]
89
+ # rubocop:enable Metrics/LineLength
90
+ end
91
+
92
+ let(:ex) { JavaAirbrakeTestError.new.tap { |e| e.set_backtrace(backtrace) } }
93
+
94
+ it "returns a properly formatted array of hashes" do
95
+ allow(described_class).to receive(:java_exception?).and_return(true)
96
+ expect(described_class.parse(ex)).to eq(parsed_backtrace)
97
+ end
98
+ end
99
+
100
+ context "JRuby non-throwable exceptions" do
101
+ let(:backtrace) do
102
+ # rubocop:disable Metrics/LineLength
103
+ ['org.postgresql.core.v3.ConnectionFactoryImpl.openConnectionImpl(org/postgresql/core/v3/ConnectionFactoryImpl.java:257)',
104
+ 'org.postgresql.core.ConnectionFactory.openConnection(org/postgresql/core/ConnectionFactory.java:65)',
105
+ 'org.postgresql.jdbc2.AbstractJdbc2Connection.<init>(org/postgresql/jdbc2/AbstractJdbc2Connection.java:149)']
106
+ # rubocop:enable Metrics/LineLength
107
+ end
108
+
109
+ let(:parsed_backtrace) do
110
+ # rubocop:disable Metrics/LineLength
111
+ [{ file: 'org/postgresql/core/v3/ConnectionFactoryImpl.java', line: 257, function: 'org.postgresql.core.v3.ConnectionFactoryImpl.openConnectionImpl' },
112
+ { file: 'org/postgresql/core/ConnectionFactory.java', line: 65, function: 'org.postgresql.core.ConnectionFactory.openConnection' },
113
+ { file: 'org/postgresql/jdbc2/AbstractJdbc2Connection.java', line: 149, function: 'org.postgresql.jdbc2.AbstractJdbc2Connection.<init>' }]
114
+ # rubocop:enable Metrics/LineLength
115
+ end
116
+
117
+ let(:ex) { AirbrakeTestError.new.tap { |e| e.set_backtrace(backtrace) } }
118
+
119
+ it "returns a properly formatted array of hashes" do
120
+ expect(described_class.parse(ex)).to eq(parsed_backtrace)
121
+ end
122
+ end
123
+
124
+ context "generic backtrace" do
125
+ context "when function is absent" do
126
+ # rubocop:disable Metrics/LineLength
127
+ let(:generic_bt) do
128
+ ["/home/bingo/bango/assets/stylesheets/error_pages.scss:139:in `animation'",
129
+ "/home/bingo/bango/assets/stylesheets/error_pages.scss:139",
130
+ "/home/bingo/.gem/ruby/2.2.2/gems/sass-3.4.20/lib/sass/tree/visitors/perform.rb:349:in `block in visit_mixin'"]
131
+ end
132
+ # rubocop:enable Metrics/LineLength
133
+
134
+ let(:ex) { AirbrakeTestError.new.tap { |e| e.set_backtrace(generic_bt) } }
135
+
136
+ let(:parsed_backtrace) do
137
+ # rubocop:disable Metrics/LineLength, Style/HashSyntax, Layout/SpaceInsideHashLiteralBraces, Layout/SpaceAroundOperators
138
+ [{:file=>"/home/bingo/bango/assets/stylesheets/error_pages.scss", :line=>139, :function=>"animation"},
139
+ {:file=>"/home/bingo/bango/assets/stylesheets/error_pages.scss", :line=>139, :function=>nil},
140
+ {:file=>"/home/bingo/.gem/ruby/2.2.2/gems/sass-3.4.20/lib/sass/tree/visitors/perform.rb", :line=>349, :function=>"block in visit_mixin"}]
141
+ # rubocop:enable Metrics/LineLength, Style/HashSyntax, Layout/SpaceInsideHashLiteralBraces, Layout/SpaceAroundOperators
142
+ end
143
+
144
+ it "returns a properly formatted array of hashes" do
145
+ expect(described_class.parse(ex)).to eq(parsed_backtrace)
146
+ end
147
+ end
148
+
149
+ context "when line is absent" do
150
+ let(:generic_bt) do
151
+ ["/Users/grammakov/repositories/weintervene/config.ru:in `new'"]
152
+ end
153
+
154
+ let(:ex) { AirbrakeTestError.new.tap { |e| e.set_backtrace(generic_bt) } }
155
+
156
+ let(:parsed_backtrace) do
157
+ [{ file: '/Users/grammakov/repositories/weintervene/config.ru',
158
+ line: nil,
159
+ function: 'new' }]
160
+ end
161
+
162
+ it "returns a properly formatted array of hashes" do
163
+ expect(described_class.parse(ex)).to eq(parsed_backtrace)
164
+ end
165
+ end
166
+ end
167
+
168
+ context "unknown backtrace" do
169
+ let(:unknown_bt) { ['a b c 1 23 321 .rb'] }
170
+
171
+ let(:ex) { AirbrakeTestError.new.tap { |e| e.set_backtrace(unknown_bt) } }
172
+
173
+ it "returns array of hashes where each unknown frame is marked as 'function'" do
174
+ expect(
175
+ described_class.parse(ex)
176
+ ).to eq([file: nil, line: nil, function: 'a b c 1 23 321 .rb'])
177
+ end
178
+
179
+ it "logs frames that cannot be parsed" do
180
+ expect(Airbrake::Loggable.instance).to receive(:error).with(
181
+ /can't parse 'a b c 1 23 321 .rb'/
182
+ )
183
+ described_class.parse(ex)
184
+ end
185
+ end
186
+
187
+ context "given a backtrace with an empty function" do
188
+ let(:bt) do
189
+ ["/airbrake-ruby/vendor/jruby/1.9/gems/rspec-core-3.4.1/exe/rspec:3:in `'"]
190
+ end
191
+
192
+ let(:ex) { AirbrakeTestError.new.tap { |e| e.set_backtrace(bt) } }
193
+
194
+ let(:parsed_backtrace) do
195
+ [{ file: '/airbrake-ruby/vendor/jruby/1.9/gems/rspec-core-3.4.1/exe/rspec',
196
+ line: 3,
197
+ function: '' }]
198
+ end
199
+
200
+ it "returns a properly formatted array of hashes" do
201
+ expect(described_class.parse(ex)).to eq(parsed_backtrace)
202
+ end
203
+ end
204
+
205
+ context "given an Oracle backtrace" do
206
+ let(:bt) do
207
+ ['ORA-06512: at "STORE.LI_LICENSES_PACK", line 1945',
208
+ 'ORA-06512: at "ACTIVATION.LI_ACT_LICENSES_PACK", line 101',
209
+ 'ORA-06512: at line 2',
210
+ 'from stmt.c:243:in oci8lib_220.bundle']
211
+ end
212
+
213
+ let(:ex) { OCIError.new.tap { |e| e.set_backtrace(bt) } }
214
+
215
+ let(:parsed_backtrace) do
216
+ [{ file: nil, line: 1945, function: 'STORE.LI_LICENSES_PACK' },
217
+ { file: nil, line: 101, function: 'ACTIVATION.LI_ACT_LICENSES_PACK' },
218
+ { file: nil, line: 2, function: nil },
219
+ { file: 'stmt.c', line: 243, function: 'oci8lib_220.bundle' }]
220
+ end
221
+
222
+ it "returns a properly formatted array of hashes" do
223
+ stub_const('OCIError', AirbrakeTestError)
224
+ expect(described_class.parse(ex)).to eq(parsed_backtrace)
225
+ end
226
+ end
227
+
228
+ context "given an ExecJS exception" do
229
+ let(:bt) do
230
+ ['compile ((execjs):6692:19)',
231
+ 'eval (<anonymous>:1:10)',
232
+ '(execjs):6703:8',
233
+ 'require../helpers.exports ((execjs):1:102)',
234
+ 'Object.<anonymous> ((execjs):1:120)',
235
+ 'Object.Module._extensions..js (module.js:550:10)',
236
+ 'bootstrap_node.js:467:3',
237
+ "/opt/rubies/ruby-2.3.1/lib/ruby/2.3.0/benchmark.rb:308:in `realtime'"]
238
+ end
239
+
240
+ let(:ex) { ExecJS::RuntimeError.new.tap { |e| e.set_backtrace(bt) } }
241
+
242
+ let(:parsed_backtrace) do
243
+ [{ file: '(execjs)', line: 6692, function: 'compile' },
244
+ { file: '<anonymous>', line: 1, function: 'eval' },
245
+ { file: '(execjs)', line: 6703, function: '' },
246
+ { file: '(execjs)', line: 1, function: 'require../helpers.exports' },
247
+ { file: '(execjs)', line: 1, function: 'Object.<anonymous>' },
248
+ { file: 'module.js', line: 550, function: 'Object.Module._extensions..js' },
249
+ { file: 'bootstrap_node.js', line: 467, function: '' },
250
+ { file: '/opt/rubies/ruby-2.3.1/lib/ruby/2.3.0/benchmark.rb',
251
+ line: 308,
252
+ function: 'realtime' }]
253
+ end
254
+
255
+ it "returns a properly formatted array of hashes" do
256
+ stub_const('ExecJS::RuntimeError', AirbrakeTestError)
257
+ expect(described_class.parse(ex)).to eq(parsed_backtrace)
258
+ end
259
+ end
260
+
261
+ context "when code hunks are enabled" do
262
+ before { Airbrake::Config.instance.merge(code_hunks: true) }
263
+
264
+ context "and when root_directory is configured" do
265
+ before do
266
+ Airbrake::Config.instance.merge(root_directory: project_root_path(''))
267
+ end
268
+
269
+ let(:parsed_backtrace) do
270
+ [
271
+ {
272
+ file: project_root_path('code.rb'),
273
+ line: 94,
274
+ function: 'to_json',
275
+ code: {
276
+ 92 => ' loop do',
277
+ 93 => ' begin',
278
+ 94 => ' json = @payload.to_json',
279
+ 95 => ' rescue *JSON_EXCEPTIONS => ex',
280
+ # rubocop:disable Metrics/LineLength,Lint/InterpolationCheck
281
+ 96 => ' @config.logger.debug("#{LOG_LABEL} `notice.to_json` failed: #{ex.class}: #{ex}")',
282
+ # rubocop:enable Metrics/LineLength,Lint/InterpolationCheck
283
+ }
284
+ },
285
+ {
286
+ file: fixture_path('notroot.txt'),
287
+ line: 3,
288
+ function: 'pineapple'
289
+ },
290
+ {
291
+ file: project_root_path('vendor/bundle/ignored_file.rb'),
292
+ line: 2,
293
+ function: 'ignore_me'
294
+ }
295
+ ]
296
+ end
297
+
298
+ it "attaches code to those frames files of which match root_directory" do
299
+ ex = RuntimeError.new
300
+ backtrace = [
301
+ project_root_path('code.rb') + ":94:in `to_json'",
302
+ fixture_path('notroot.txt' + ":3:in `pineapple'"),
303
+ project_root_path('vendor/bundle/ignored_file.rb') + ":2:in `ignore_me'"
304
+ ]
305
+ ex.set_backtrace(backtrace)
306
+ expect(described_class.parse(ex)).to eq(parsed_backtrace)
307
+ end
308
+ end
309
+
310
+ context "and when root_directory is a Pathname" do
311
+ before do
312
+ Airbrake::Config.instance.merge(
313
+ root_directory: Pathname.new(project_root_path(''))
314
+ )
315
+ end
316
+
317
+ let(:parsed_backtrace) do
318
+ [
319
+ {
320
+ file: project_root_path('code.rb'),
321
+ line: 94,
322
+ function: 'to_json',
323
+ code: {
324
+ 92 => ' loop do',
325
+ 93 => ' begin',
326
+ 94 => ' json = @payload.to_json',
327
+ 95 => ' rescue *JSON_EXCEPTIONS => ex',
328
+ # rubocop:disable Metrics/LineLength,Lint/InterpolationCheck
329
+ 96 => ' @config.logger.debug("#{LOG_LABEL} `notice.to_json` failed: #{ex.class}: #{ex}")',
330
+ # rubocop:enable Metrics/LineLength,Lint/InterpolationCheck
331
+ }
332
+ }
333
+ ]
334
+ end
335
+
336
+ it "attaches code to those frames files of which match root_directory" do
337
+ ex = RuntimeError.new
338
+ ex.set_backtrace([project_root_path('code.rb') + ":94:in `to_json'"])
339
+ expect(described_class.parse(ex)).to eq(parsed_backtrace)
340
+ end
341
+ end
342
+
343
+ context "and when root_directory isn't configured" do
344
+ before do
345
+ Airbrake::Config.instance.merge(root_directory: nil)
346
+ stub_const('Airbrake::Backtrace::CODE_FRAME_LIMIT', 2)
347
+ end
348
+
349
+ let(:parsed_backtrace) do
350
+ [
351
+ {
352
+ file: project_root_path('code.rb'),
353
+ line: 94,
354
+ function: 'to_json',
355
+ code: {
356
+ 92 => ' loop do',
357
+ 93 => ' begin',
358
+ 94 => ' json = @payload.to_json',
359
+ 95 => ' rescue *JSON_EXCEPTIONS => ex',
360
+ # rubocop:disable Metrics/LineLength,Lint/InterpolationCheck
361
+ 96 => ' @config.logger.debug("#{LOG_LABEL} `notice.to_json` failed: #{ex.class}: #{ex}")',
362
+ # rubocop:enable Metrics/LineLength,Lint/InterpolationCheck
363
+ }
364
+ },
365
+ {
366
+ file: project_root_path('code.rb'),
367
+ line: 95,
368
+ function: 'to_json',
369
+ code: {
370
+ 93 => ' begin',
371
+ 94 => ' json = @payload.to_json',
372
+ 95 => ' rescue *JSON_EXCEPTIONS => ex',
373
+ # rubocop:disable Metrics/LineLength,Lint/InterpolationCheck
374
+ 96 => ' @config.logger.debug("#{LOG_LABEL} `notice.to_json` failed: #{ex.class}: #{ex}")',
375
+ # rubocop:enable Metrics/LineLength,Lint/InterpolationCheck
376
+ 97 => ' else'
377
+ }
378
+ },
379
+ {
380
+ file: project_root_path('code.rb'),
381
+ line: 96,
382
+ function: 'to_json'
383
+ }
384
+ ]
385
+ end
386
+
387
+ it "attaches code to first N frames" do
388
+ ex = RuntimeError.new
389
+ backtrace = [
390
+ project_root_path('code.rb') + ":94:in `to_json'",
391
+ project_root_path('code.rb') + ":95:in `to_json'",
392
+ project_root_path('code.rb') + ":96:in `to_json'"
393
+ ]
394
+ ex.set_backtrace(backtrace)
395
+ expect(described_class.parse(ex)).to eq(parsed_backtrace)
396
+ end
397
+ end
398
+ end
399
+
400
+ context "when code hunks are disabled" do
401
+ before { Airbrake::Config.instance.merge(code_hunks: false) }
402
+
403
+ context "and when root_directory is configured" do
404
+ before do
405
+ Airbrake::Config.instance.merge(root_directory: project_root_path(''))
406
+ end
407
+
408
+ let(:parsed_backtrace) do
409
+ [
410
+ {
411
+ file: project_root_path('code.rb'),
412
+ line: 94,
413
+ function: 'to_json'
414
+ }
415
+ ]
416
+ end
417
+
418
+ it "doesn't attach code to frames" do
419
+ ex = RuntimeError.new
420
+ backtrace = [project_root_path('code.rb') + ":94:in `to_json'"]
421
+ ex.set_backtrace(backtrace)
422
+ expect(described_class.parse(ex)).to eq(parsed_backtrace)
423
+ end
424
+ end
425
+ end
426
+ end
427
+ end