airbrake-ruby 4.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. checksums.yaml +7 -0
  2. data/lib/airbrake-ruby.rb +513 -0
  3. data/lib/airbrake-ruby/async_sender.rb +142 -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 +48 -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 +104 -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 +45 -0
  35. data/lib/airbrake-ruby/performance_notifier.rb +125 -0
  36. data/lib/airbrake-ruby/promise.rb +109 -0
  37. data/lib/airbrake-ruby/query.rb +53 -0
  38. data/lib/airbrake-ruby/request.rb +45 -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/time_truncate.rb +17 -0
  45. data/lib/airbrake-ruby/timed_trace.rb +58 -0
  46. data/lib/airbrake-ruby/truncator.rb +115 -0
  47. data/lib/airbrake-ruby/version.rb +6 -0
  48. data/spec/airbrake_spec.rb +324 -0
  49. data/spec/async_sender_spec.rb +155 -0
  50. data/spec/backtrace_spec.rb +427 -0
  51. data/spec/benchmark_spec.rb +33 -0
  52. data/spec/code_hunk_spec.rb +115 -0
  53. data/spec/config/validator_spec.rb +184 -0
  54. data/spec/config_spec.rb +154 -0
  55. data/spec/deploy_notifier_spec.rb +48 -0
  56. data/spec/file_cache.rb +36 -0
  57. data/spec/filter_chain_spec.rb +92 -0
  58. data/spec/filters/context_filter_spec.rb +23 -0
  59. data/spec/filters/dependency_filter_spec.rb +12 -0
  60. data/spec/filters/exception_attributes_filter_spec.rb +50 -0
  61. data/spec/filters/gem_root_filter_spec.rb +41 -0
  62. data/spec/filters/git_last_checkout_filter_spec.rb +46 -0
  63. data/spec/filters/git_repository_filter.rb +61 -0
  64. data/spec/filters/git_revision_filter_spec.rb +126 -0
  65. data/spec/filters/keys_blacklist_spec.rb +225 -0
  66. data/spec/filters/keys_whitelist_spec.rb +194 -0
  67. data/spec/filters/root_directory_filter_spec.rb +39 -0
  68. data/spec/filters/sql_filter_spec.rb +219 -0
  69. data/spec/filters/system_exit_filter_spec.rb +14 -0
  70. data/spec/filters/thread_filter_spec.rb +277 -0
  71. data/spec/fixtures/notroot.txt +7 -0
  72. data/spec/fixtures/project_root/code.rb +221 -0
  73. data/spec/fixtures/project_root/empty_file.rb +0 -0
  74. data/spec/fixtures/project_root/long_line.txt +1 -0
  75. data/spec/fixtures/project_root/short_file.rb +3 -0
  76. data/spec/fixtures/project_root/vendor/bundle/ignored_file.rb +5 -0
  77. data/spec/helpers.rb +9 -0
  78. data/spec/ignorable_spec.rb +14 -0
  79. data/spec/inspectable_spec.rb +45 -0
  80. data/spec/monotonic_time_spec.rb +12 -0
  81. data/spec/nested_exception_spec.rb +73 -0
  82. data/spec/notice_notifier_spec.rb +356 -0
  83. data/spec/notice_notifier_spec/options_spec.rb +259 -0
  84. data/spec/notice_spec.rb +296 -0
  85. data/spec/performance_breakdown_spec.rb +12 -0
  86. data/spec/performance_notifier_spec.rb +435 -0
  87. data/spec/promise_spec.rb +197 -0
  88. data/spec/query_spec.rb +11 -0
  89. data/spec/request_spec.rb +11 -0
  90. data/spec/response_spec.rb +88 -0
  91. data/spec/spec_helper.rb +100 -0
  92. data/spec/stashable_spec.rb +23 -0
  93. data/spec/stat_spec.rb +47 -0
  94. data/spec/sync_sender_spec.rb +133 -0
  95. data/spec/tdigest_spec.rb +230 -0
  96. data/spec/time_truncate_spec.rb +13 -0
  97. data/spec/timed_trace_spec.rb +125 -0
  98. data/spec/truncator_spec.rb +238 -0
  99. metadata +213 -0
@@ -0,0 +1,155 @@
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: queue_size
12
+ )
13
+
14
+ allow(Airbrake::Loggable.instance).to receive(:debug)
15
+ expect(subject).to have_workers
16
+ end
17
+
18
+ describe "#send" do
19
+ it "sends payload to Airbrake" do
20
+ 2.times do
21
+ subject.send(notice, Airbrake::Promise.new)
22
+ end
23
+ subject.close
24
+
25
+ expect(a_request(:post, endpoint)).to have_been_made.twice
26
+ end
27
+
28
+ context "when the queue is full" do
29
+ before do
30
+ allow(subject.unsent).to receive(:size).and_return(queue_size)
31
+ end
32
+
33
+ it "discards payload" do
34
+ 200.times do
35
+ subject.send(notice, Airbrake::Promise.new)
36
+ end
37
+ subject.close
38
+
39
+ expect(a_request(:post, endpoint)).not_to have_been_made
40
+ end
41
+
42
+ it "logs discarded payload" do
43
+ expect(Airbrake::Loggable.instance).to receive(:error).with(
44
+ /reached its capacity/
45
+ ).exactly(15).times
46
+
47
+ 15.times do
48
+ subject.send(notice, Airbrake::Promise.new)
49
+ end
50
+ subject.close
51
+ end
52
+
53
+ it "returns a rejected promise" do
54
+ promise = Airbrake::Promise.new
55
+ 200.times { subject.send(notice, promise) }
56
+ expect(promise.value).to eq(
57
+ 'error' => "AsyncSender has reached its capacity of #{queue_size}"
58
+ )
59
+ end
60
+ end
61
+ end
62
+
63
+ describe "#close" do
64
+ context "when there are no unsent notices" do
65
+ it "joins the spawned thread" do
66
+ workers = subject.workers.list
67
+ expect(workers).to all(be_alive)
68
+
69
+ subject.close
70
+ expect(workers).to all(be_stop)
71
+ end
72
+ end
73
+
74
+ context "when there are some unsent notices" do
75
+ it "logs how many notices are left to send" do
76
+ expect(Airbrake::Loggable.instance).to receive(:debug).with(
77
+ /waiting to send \d+ unsent notice\(s\)/
78
+ )
79
+ expect(Airbrake::Loggable.instance).to receive(:debug).with(/closed/)
80
+
81
+ 300.times { subject.send(notice, Airbrake::Promise.new) }
82
+ subject.close
83
+ end
84
+
85
+ it "waits until the unsent notices queue is empty" do
86
+ subject.close
87
+ expect(subject.unsent.size).to be_zero
88
+ end
89
+ end
90
+
91
+ context "when it was already closed" do
92
+ it "doesn't increase the unsent queue size" do
93
+ begin
94
+ subject.close
95
+ rescue Airbrake::Error
96
+ nil
97
+ end
98
+
99
+ expect(subject.unsent.size).to be_zero
100
+ end
101
+
102
+ it "raises error" do
103
+ subject.close
104
+
105
+ expect(subject).to be_closed
106
+ expect { subject.close }.to raise_error(
107
+ Airbrake::Error, 'attempted to close already closed sender'
108
+ )
109
+ end
110
+ end
111
+
112
+ context "when workers were not spawned" do
113
+ it "correctly closes the notifier nevertheless" do
114
+ subject.close
115
+ expect(subject).to be_closed
116
+ end
117
+ end
118
+ end
119
+
120
+ describe "#has_workers?" do
121
+ it "returns false when the sender is not closed, but has 0 workers" do
122
+ subject.workers.list.each do |worker|
123
+ worker.kill.join
124
+ end
125
+ expect(subject).not_to have_workers
126
+ end
127
+
128
+ it "returns false when the sender is closed" do
129
+ subject.close
130
+ expect(subject).not_to have_workers
131
+ end
132
+
133
+ it "respawns workers on fork()", skip: %w[jruby rbx].include?(RUBY_ENGINE) do
134
+ pid = fork { expect(subject).to have_workers }
135
+ Process.wait(pid)
136
+ subject.close
137
+ expect(subject).not_to have_workers
138
+ end
139
+ end
140
+
141
+ describe "#spawn_workers" do
142
+ it "spawns alive threads in an enclosed ThreadGroup" do
143
+ expect(subject.workers).to be_a(ThreadGroup)
144
+ expect(subject.workers.list).to all(be_alive)
145
+ expect(subject.workers).to be_enclosed
146
+
147
+ subject.close
148
+ end
149
+
150
+ it "spawns exactly config.workers workers" do
151
+ expect(subject.workers.list.size).to eq(Airbrake::Config.instance.workers)
152
+ subject.close
153
+ end
154
+ end
155
+ 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