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,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