airbrake-ruby 3.2.2-java

Sign up to get free protection for your applications and to get access to all the features.
Files changed (82) hide show
  1. checksums.yaml +7 -0
  2. data/lib/airbrake-ruby.rb +554 -0
  3. data/lib/airbrake-ruby/async_sender.rb +119 -0
  4. data/lib/airbrake-ruby/backtrace.rb +194 -0
  5. data/lib/airbrake-ruby/code_hunk.rb +53 -0
  6. data/lib/airbrake-ruby/config.rb +238 -0
  7. data/lib/airbrake-ruby/config/validator.rb +63 -0
  8. data/lib/airbrake-ruby/deploy_notifier.rb +47 -0
  9. data/lib/airbrake-ruby/file_cache.rb +48 -0
  10. data/lib/airbrake-ruby/filter_chain.rb +95 -0
  11. data/lib/airbrake-ruby/filters/context_filter.rb +29 -0
  12. data/lib/airbrake-ruby/filters/dependency_filter.rb +31 -0
  13. data/lib/airbrake-ruby/filters/exception_attributes_filter.rb +45 -0
  14. data/lib/airbrake-ruby/filters/gem_root_filter.rb +33 -0
  15. data/lib/airbrake-ruby/filters/git_last_checkout_filter.rb +90 -0
  16. data/lib/airbrake-ruby/filters/git_repository_filter.rb +42 -0
  17. data/lib/airbrake-ruby/filters/git_revision_filter.rb +66 -0
  18. data/lib/airbrake-ruby/filters/keys_blacklist.rb +50 -0
  19. data/lib/airbrake-ruby/filters/keys_filter.rb +140 -0
  20. data/lib/airbrake-ruby/filters/keys_whitelist.rb +49 -0
  21. data/lib/airbrake-ruby/filters/root_directory_filter.rb +28 -0
  22. data/lib/airbrake-ruby/filters/sql_filter.rb +104 -0
  23. data/lib/airbrake-ruby/filters/system_exit_filter.rb +23 -0
  24. data/lib/airbrake-ruby/filters/thread_filter.rb +92 -0
  25. data/lib/airbrake-ruby/hash_keyable.rb +37 -0
  26. data/lib/airbrake-ruby/ignorable.rb +44 -0
  27. data/lib/airbrake-ruby/nested_exception.rb +39 -0
  28. data/lib/airbrake-ruby/notice.rb +165 -0
  29. data/lib/airbrake-ruby/notice_notifier.rb +228 -0
  30. data/lib/airbrake-ruby/performance_notifier.rb +161 -0
  31. data/lib/airbrake-ruby/promise.rb +99 -0
  32. data/lib/airbrake-ruby/response.rb +71 -0
  33. data/lib/airbrake-ruby/stat.rb +56 -0
  34. data/lib/airbrake-ruby/sync_sender.rb +111 -0
  35. data/lib/airbrake-ruby/tdigest.rb +393 -0
  36. data/lib/airbrake-ruby/time_truncate.rb +17 -0
  37. data/lib/airbrake-ruby/truncator.rb +115 -0
  38. data/lib/airbrake-ruby/version.rb +6 -0
  39. data/spec/airbrake_spec.rb +171 -0
  40. data/spec/async_sender_spec.rb +154 -0
  41. data/spec/backtrace_spec.rb +438 -0
  42. data/spec/code_hunk_spec.rb +118 -0
  43. data/spec/config/validator_spec.rb +189 -0
  44. data/spec/config_spec.rb +281 -0
  45. data/spec/deploy_notifier_spec.rb +41 -0
  46. data/spec/file_cache.rb +36 -0
  47. data/spec/filter_chain_spec.rb +83 -0
  48. data/spec/filters/context_filter_spec.rb +25 -0
  49. data/spec/filters/dependency_filter_spec.rb +14 -0
  50. data/spec/filters/exception_attributes_filter_spec.rb +63 -0
  51. data/spec/filters/gem_root_filter_spec.rb +44 -0
  52. data/spec/filters/git_last_checkout_filter_spec.rb +48 -0
  53. data/spec/filters/git_repository_filter.rb +53 -0
  54. data/spec/filters/git_revision_filter_spec.rb +126 -0
  55. data/spec/filters/keys_blacklist_spec.rb +236 -0
  56. data/spec/filters/keys_whitelist_spec.rb +205 -0
  57. data/spec/filters/root_directory_filter_spec.rb +42 -0
  58. data/spec/filters/sql_filter_spec.rb +219 -0
  59. data/spec/filters/system_exit_filter_spec.rb +14 -0
  60. data/spec/filters/thread_filter_spec.rb +279 -0
  61. data/spec/fixtures/notroot.txt +7 -0
  62. data/spec/fixtures/project_root/code.rb +221 -0
  63. data/spec/fixtures/project_root/empty_file.rb +0 -0
  64. data/spec/fixtures/project_root/long_line.txt +1 -0
  65. data/spec/fixtures/project_root/short_file.rb +3 -0
  66. data/spec/fixtures/project_root/vendor/bundle/ignored_file.rb +5 -0
  67. data/spec/helpers.rb +9 -0
  68. data/spec/ignorable_spec.rb +14 -0
  69. data/spec/nested_exception_spec.rb +75 -0
  70. data/spec/notice_notifier_spec.rb +436 -0
  71. data/spec/notice_notifier_spec/options_spec.rb +266 -0
  72. data/spec/notice_spec.rb +297 -0
  73. data/spec/performance_notifier_spec.rb +287 -0
  74. data/spec/promise_spec.rb +165 -0
  75. data/spec/response_spec.rb +82 -0
  76. data/spec/spec_helper.rb +102 -0
  77. data/spec/stat_spec.rb +35 -0
  78. data/spec/sync_sender_spec.rb +140 -0
  79. data/spec/tdigest_spec.rb +230 -0
  80. data/spec/time_truncate_spec.rb +13 -0
  81. data/spec/truncator_spec.rb +238 -0
  82. metadata +278 -0
@@ -0,0 +1,6 @@
1
+ # We use Semantic Versioning v2.0.0
2
+ # More information: http://semver.org/
3
+ module Airbrake
4
+ # @return [String] the library version
5
+ AIRBRAKE_RUBY_VERSION = '3.2.2'.freeze
6
+ end
@@ -0,0 +1,171 @@
1
+ RSpec.describe Airbrake do
2
+ describe ".[]" do
3
+ it "returns a NilNotifier" do
4
+ expect(described_class[:test]).to be_an(Airbrake::NilNoticeNotifier)
5
+ end
6
+ end
7
+
8
+ describe ".notifiers" do
9
+ it "returns a Hash of notifiers" do
10
+ expect(described_class.notifiers).to eq(
11
+ notice: {}, performance: {}, deploy: {}
12
+ )
13
+ end
14
+ end
15
+
16
+ let(:default_notifier) do
17
+ described_class[:default]
18
+ end
19
+
20
+ describe ".configure" do
21
+ let(:config_params) { { project_id: 1, project_key: 'abc' } }
22
+
23
+ after do
24
+ described_class.instance_variable_get(:@notice_notifiers).clear
25
+ described_class.instance_variable_get(:@performance_notifiers).clear
26
+ described_class.instance_variable_get(:@deploy_notifiers).clear
27
+ end
28
+
29
+ it "yields the config" do
30
+ expect do |b|
31
+ begin
32
+ described_class.configure(&b)
33
+ rescue Airbrake::Error
34
+ nil
35
+ end
36
+ end.to yield_with_args(Airbrake::Config)
37
+ end
38
+
39
+ context "when invoked with a notice notifier name" do
40
+ it "sets notice notifier name to the provided name" do
41
+ described_class.configure(:test) { |c| c.merge(config_params) }
42
+ expect(described_class[:test]).to be_an(Airbrake::NoticeNotifier)
43
+ end
44
+ end
45
+
46
+ context "when invoked without a notifier name" do
47
+ it "defaults to the :default notifier name" do
48
+ described_class.configure { |c| c.merge(config_params) }
49
+ expect(described_class[:default]).to be_an(Airbrake::NoticeNotifier)
50
+ end
51
+ end
52
+
53
+ context "when invoked twice with the same notifier name" do
54
+ it "raises Airbrake::Error" do
55
+ described_class.configure { |c| c.merge(config_params) }
56
+ expect do
57
+ described_class.configure { |c| c.merge(config_params) }
58
+ end.to raise_error(
59
+ Airbrake::Error, "the 'default' notifier was already configured"
60
+ )
61
+ end
62
+ end
63
+
64
+ context "when user config doesn't contain a project id" do
65
+ it "raises error" do
66
+ expect { described_class.configure { |c| c.project_key = '1' } }.
67
+ to raise_error(Airbrake::Error, ':project_id is required')
68
+ end
69
+ end
70
+
71
+ context "when user config doesn't contain a project key" do
72
+ it "raises error" do
73
+ expect { described_class.configure { |c| c.project_id = 1 } }.
74
+ to raise_error(Airbrake::Error, ':project_key is required')
75
+ end
76
+ end
77
+ end
78
+
79
+ describe ".configured?" do
80
+ it "forwards 'configured?' to the notifier" do
81
+ expect(default_notifier).to receive(:configured?)
82
+ described_class.configured?
83
+ end
84
+ end
85
+
86
+ describe ".notify" do
87
+ it "forwards 'notify' to the notifier" do
88
+ block = proc {}
89
+ expect(default_notifier).to receive(:notify).with('ex', foo: 'bar', &block)
90
+ described_class.notify('ex', foo: 'bar', &block)
91
+ end
92
+ end
93
+
94
+ describe ".notify_sync" do
95
+ it "forwards 'notify_sync' to the notifier" do
96
+ block = proc {}
97
+ expect(default_notifier).to receive(:notify).with('ex', foo: 'bar', &block)
98
+ described_class.notify('ex', foo: 'bar', &block)
99
+ end
100
+ end
101
+
102
+ describe ".add_filter" do
103
+ it "forwards 'add_filter' to the notifier" do
104
+ block = proc {}
105
+ expect(default_notifier).to receive(:add_filter).with(nil, &block)
106
+ described_class.add_filter(&block)
107
+ end
108
+ end
109
+
110
+ describe ".build_notice" do
111
+ it "forwards 'build_notice' to the notifier" do
112
+ expect(default_notifier).to receive(:build_notice).with('ex', foo: 'bar')
113
+ described_class.build_notice('ex', foo: 'bar')
114
+ end
115
+ end
116
+
117
+ describe ".close" do
118
+ it "forwards 'close' to the notifier" do
119
+ expect(default_notifier).to receive(:close)
120
+ described_class.close
121
+ end
122
+ end
123
+
124
+ describe ".create_deploy" do
125
+ let(:default_notifier) { described_class.notifiers[:deploy][:default] }
126
+
127
+ it "calls 'notify' on the deploy notifier" do
128
+ expect(default_notifier).to receive(:notify).with(foo: 'bar')
129
+ described_class.create_deploy(foo: 'bar')
130
+ end
131
+ end
132
+
133
+ describe ".merge_context" do
134
+ it "forwards 'merge_context' to the notifier" do
135
+ expect(default_notifier).to receive(:merge_context).with(foo: 'bar')
136
+ described_class.merge_context(foo: 'bar')
137
+ end
138
+ end
139
+
140
+ describe ".notify_request" do
141
+ let(:default_notifier) { described_class.notifiers[:performance][:default] }
142
+
143
+ it "calls 'notify' on the route notifier" do
144
+ params = {
145
+ method: 'GET',
146
+ route: '/foo',
147
+ status_code: 200,
148
+ start_time: Time.new(2018, 1, 1, 0, 20, 0, 0),
149
+ end_time: Time.new(2018, 1, 1, 0, 19, 0, 0)
150
+ }
151
+ expect(default_notifier).to receive(:notify).with(Airbrake::Request.new(params))
152
+ described_class.notify_request(params)
153
+ end
154
+ end
155
+
156
+ describe ".notify_query" do
157
+ let(:default_notifier) { described_class.notifiers[:performance][:default] }
158
+
159
+ it "calls 'notify' on the query notifier" do
160
+ params = {
161
+ method: 'GET',
162
+ route: '/foo',
163
+ query: 'SELECT * FROM foos',
164
+ start_time: Time.new(2018, 1, 1, 0, 20, 0, 0),
165
+ end_time: Time.new(2018, 1, 1, 0, 19, 0, 0)
166
+ }
167
+ expect(default_notifier).to receive(:notify).with(Airbrake::Query.new(params))
168
+ described_class.notify_query(params)
169
+ end
170
+ end
171
+ end
@@ -0,0 +1,154 @@
1
+ RSpec.describe Airbrake::AsyncSender do
2
+ before do
3
+ stub_request(:post, /.*/).to_return(status: 201, body: '{}')
4
+ end
5
+
6
+ describe "#send" do
7
+ it "limits the size of the queue" do
8
+ stdout = StringIO.new
9
+ notices_count = 1000
10
+ queue_size = 10
11
+ config = Airbrake::Config.new(
12
+ logger: Logger.new(stdout), workers: 3, queue_size: queue_size
13
+ )
14
+ sender = described_class.new(config)
15
+ expect(sender).to have_workers
16
+
17
+ notice = Airbrake::Notice.new(config, AirbrakeTestError.new)
18
+ notices_count.times { sender.send(notice, Airbrake::Promise.new) }
19
+ sender.close
20
+
21
+ log = stdout.string.split("\n")
22
+ notices_sent = log.grep(/\*\*Airbrake: Airbrake::Response \(201\): \{\}/).size
23
+ notices_dropped = log.grep(/\*\*Airbrake:.*not.*delivered/).size
24
+ expect(notices_sent).to be >= queue_size
25
+ expect(notices_sent + notices_dropped).to eq(notices_count)
26
+ end
27
+ end
28
+
29
+ describe "#close" do
30
+ before do
31
+ @stderr = StringIO.new
32
+ config = Airbrake::Config.new(logger: Logger.new(@stderr))
33
+ @sender = described_class.new(config)
34
+ expect(@sender).to have_workers
35
+ end
36
+
37
+ context "when there are no unsent notices" do
38
+ it "joins the spawned thread" do
39
+ workers = @sender.instance_variable_get(:@workers).list
40
+
41
+ expect(workers).to all(be_alive)
42
+ @sender.close
43
+ expect(workers).to all(be_stop)
44
+ end
45
+ end
46
+
47
+ context "when there are some unsent notices" do
48
+ before do
49
+ notice = Airbrake::Notice.new(Airbrake::Config.new, AirbrakeTestError.new)
50
+ 300.times { @sender.send(notice, Airbrake::Promise.new) }
51
+ expect(@sender.instance_variable_get(:@unsent).size).not_to be_zero
52
+ @sender.close
53
+ end
54
+
55
+ it "warns about the number of notices" do
56
+ expect(@stderr.string).to match(/waiting to send \d+ unsent notice/)
57
+ end
58
+
59
+ it "prints the correct number of log messages" do
60
+ log = @stderr.string.split("\n")
61
+ notices_sent = log.grep(/\*\*Airbrake: Airbrake::Response \(201\): \{\}/).size
62
+ notices_dropped = log.grep(/\*\*Airbrake:.*not.*delivered/).size
63
+ expect(notices_sent).to be >= @sender.instance_variable_get(:@unsent).max
64
+ expect(notices_sent + notices_dropped).to eq(300)
65
+ end
66
+
67
+ it "waits until the unsent notices queue is empty" do
68
+ expect(@sender.instance_variable_get(:@unsent).size).to be_zero
69
+ end
70
+ end
71
+
72
+ context "when it was already closed" do
73
+ it "doesn't increase the unsent queue size" do
74
+ begin
75
+ @sender.close
76
+ rescue Airbrake::Error
77
+ nil
78
+ end
79
+
80
+ expect(@sender.instance_variable_get(:@unsent).size).to be_zero
81
+ end
82
+
83
+ it "raises error" do
84
+ @sender.close
85
+
86
+ expect(@sender).to be_closed
87
+ expect { @sender.close }.
88
+ to raise_error(Airbrake::Error, 'attempted to close already closed sender')
89
+ end
90
+ end
91
+
92
+ context "when workers were not spawned" do
93
+ it "correctly closes the notifier nevertheless" do
94
+ sender = described_class.new(Airbrake::Config.new)
95
+ sender.close
96
+
97
+ expect(sender).to be_closed
98
+ end
99
+ end
100
+ end
101
+
102
+ describe "#has_workers?" do
103
+ before do
104
+ @sender = described_class.new(Airbrake::Config.new)
105
+ expect(@sender).to have_workers
106
+ end
107
+
108
+ it "returns false when the sender is not closed, but has 0 workers" do
109
+ @sender.instance_variable_get(:@workers).list.each(&:kill)
110
+ sleep 1
111
+ expect(@sender).not_to have_workers
112
+ end
113
+
114
+ it "returns false when the sender is closed" do
115
+ @sender.close
116
+ expect(@sender).not_to have_workers
117
+ end
118
+
119
+ it "respawns workers on fork()", skip: %w[jruby rbx].include?(RUBY_ENGINE) do
120
+ pid = fork do
121
+ expect(@sender).to have_workers
122
+ end
123
+ Process.wait(pid)
124
+ @sender.close
125
+ expect(@sender).not_to have_workers
126
+ end
127
+ end
128
+
129
+ describe "#spawn_workers" do
130
+ it "spawns alive threads in an enclosed ThreadGroup" do
131
+ sender = described_class.new(Airbrake::Config.new)
132
+ expect(sender).to have_workers
133
+
134
+ workers = sender.instance_variable_get(:@workers)
135
+
136
+ expect(workers).to be_a(ThreadGroup)
137
+ expect(workers.list).to all(be_alive)
138
+ expect(workers).to be_enclosed
139
+
140
+ sender.close
141
+ end
142
+
143
+ it "spawns exactly config.workers workers" do
144
+ workers_count = 5
145
+ sender = described_class.new(Airbrake::Config.new(workers: workers_count))
146
+ expect(sender).to have_workers
147
+
148
+ workers = sender.instance_variable_get(:@workers)
149
+
150
+ expect(workers.list.size).to eq(workers_count)
151
+ sender.close
152
+ end
153
+ end
154
+ end
@@ -0,0 +1,438 @@
1
+ RSpec.describe Airbrake::Backtrace do
2
+ let(:config) do
3
+ Airbrake::Config.new.tap { |c| c.logger = Logger.new('/dev/null') }
4
+ end
5
+
6
+ describe ".parse" do
7
+ context "UNIX backtrace" do
8
+ let(:parsed_backtrace) do
9
+ # rubocop:disable Metrics/LineLength, Style/HashSyntax, Layout/SpaceAroundOperators, Layout/SpaceInsideHashLiteralBraces
10
+ [{:file=>"/home/kyrylo/code/airbrake/ruby/spec/spec_helper.rb", :line=>23, :function=>"<top (required)>"},
11
+ {:file=>"/opt/rubies/ruby-2.2.2/lib/ruby/2.2.0/rubygems/core_ext/kernel_require.rb", :line=>54, :function=>"require"},
12
+ {:file=>"/opt/rubies/ruby-2.2.2/lib/ruby/2.2.0/rubygems/core_ext/kernel_require.rb", :line=>54, :function=>"require"},
13
+ {:file=>"/home/kyrylo/code/airbrake/ruby/spec/airbrake_spec.rb", :line=>1, :function=>"<top (required)>"},
14
+ {:file=>"/home/kyrylo/.gem/ruby/2.2.2/gems/rspec-core-3.3.2/lib/rspec/core/configuration.rb", :line=>1327, :function=>"load"},
15
+ {: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"},
16
+ {:file=>"/home/kyrylo/.gem/ruby/2.2.2/gems/rspec-core-3.3.2/lib/rspec/core/configuration.rb", :line=>1325, :function=>"each"},
17
+ {: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"},
18
+ {:file=>"/home/kyrylo/.gem/ruby/2.2.2/gems/rspec-core-3.3.2/lib/rspec/core/runner.rb", :line=>102, :function=>"setup"},
19
+ {:file=>"/home/kyrylo/.gem/ruby/2.2.2/gems/rspec-core-3.3.2/lib/rspec/core/runner.rb", :line=>88, :function=>"run"},
20
+ {:file=>"/home/kyrylo/.gem/ruby/2.2.2/gems/rspec-core-3.3.2/lib/rspec/core/runner.rb", :line=>73, :function=>"run"},
21
+ {:file=>"/home/kyrylo/.gem/ruby/2.2.2/gems/rspec-core-3.3.2/lib/rspec/core/runner.rb", :line=>41, :function=>"invoke"},
22
+ {:file=>"/home/kyrylo/.gem/ruby/2.2.2/gems/rspec-core-3.3.2/exe/rspec", :line=>4, :function=>"<main>"}]
23
+ # rubocop:enable Metrics/LineLength, Style/HashSyntax, Layout/SpaceAroundOperators, Layout/SpaceInsideHashLiteralBraces
24
+ end
25
+
26
+ it "returns a properly formatted array of hashes" do
27
+ expect(
28
+ described_class.parse(config, AirbrakeTestError.new)
29
+ ).to eq(parsed_backtrace)
30
+ end
31
+ end
32
+
33
+ context "Windows backtrace" do
34
+ let(:windows_bt) do
35
+ ["C:/Program Files/Server/app/models/user.rb:13:in `magic'",
36
+ "C:/Program Files/Server/app/controllers/users_controller.rb:8:in `index'"]
37
+ end
38
+
39
+ let(:ex) { AirbrakeTestError.new.tap { |e| e.set_backtrace(windows_bt) } }
40
+
41
+ let(:parsed_backtrace) do
42
+ # rubocop:disable Metrics/LineLength, Style/HashSyntax, Layout/SpaceInsideHashLiteralBraces, Layout/SpaceAroundOperators
43
+ [{:file=>"C:/Program Files/Server/app/models/user.rb", :line=>13, :function=>"magic"},
44
+ {:file=>"C:/Program Files/Server/app/controllers/users_controller.rb", :line=>8, :function=>"index"}]
45
+ # rubocop:enable Metrics/LineLength, Style/HashSyntax, Layout/SpaceInsideHashLiteralBraces, Layout/SpaceAroundOperators
46
+ end
47
+
48
+ it "returns a properly formatted array of hashes" do
49
+ expect(described_class.parse(config, ex)).to eq(parsed_backtrace)
50
+ end
51
+ end
52
+
53
+ context "JRuby Java exceptions" do
54
+ let(:backtrace_array) do
55
+ # rubocop:disable Metrics/LineLength, Style/HashSyntax, Layout/SpaceInsideHashLiteralBraces, Layout/SpaceAroundOperators
56
+ [{:file=>"InstanceMethodInvoker.java", :line=>26, :function=>"org.jruby.java.invokers.InstanceMethodInvoker.call"},
57
+ {:file=>"Interpreter.java", :line=>126, :function=>"org.jruby.ir.interpreter.Interpreter.INTERPRET_EVAL"},
58
+ {:file=>"RubyKernel$INVOKER$s$0$3$eval19.gen", :line=>nil, :function=>"org.jruby.RubyKernel$INVOKER$s$0$3$eval19.call"},
59
+ {:file=>"RubyKernel$INVOKER$s$0$0$loop.gen", :line=>nil, :function=>"org.jruby.RubyKernel$INVOKER$s$0$0$loop.call"},
60
+ {:file=>"IRBlockBody.java", :line=>139, :function=>"org.jruby.runtime.IRBlockBody.doYield"},
61
+ {:file=>"RubyKernel$INVOKER$s$rbCatch19.gen", :line=>nil, :function=>"org.jruby.RubyKernel$INVOKER$s$rbCatch19.call"},
62
+ {: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"},
63
+ {: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"},
64
+ {:file=>"Compiler.java", :line=>111, :function=>"org.jruby.ir.Compiler$1.load"},
65
+ {:file=>"Main.java", :line=>225, :function=>"org.jruby.Main.run"},
66
+ {:file=>"Main.java", :line=>197, :function=>"org.jruby.Main.main"}]
67
+ # rubocop:enable Metrics/LineLength, Style/HashSyntax, Layout/SpaceInsideHashLiteralBraces, Layout/SpaceAroundOperators
68
+ end
69
+
70
+ it "returns a properly formatted array of hashes" do
71
+ allow(described_class).to receive(:java_exception?).and_return(true)
72
+
73
+ expect(
74
+ described_class.parse(config, JavaAirbrakeTestError.new)
75
+ ).to eq(backtrace_array)
76
+ end
77
+ end
78
+
79
+ context "JRuby classloader exceptions" do
80
+ let(:backtrace) do
81
+ # rubocop:disable Metrics/LineLength
82
+ ['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)',
83
+ 'bin.processors.image_uploader.block in make_streams(bin/processors/image_uploader.rb:21)',
84
+ '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)',
85
+ 'tmp.jruby9022301782566983632extract.$dot.META_minus_INF.rails.file(/tmp/jruby9022301782566983632extract/./META-INF/rails.rb:13)']
86
+ # rubocop:enable Metrics/LineLength
87
+ end
88
+
89
+ let(:parsed_backtrace) do
90
+ # rubocop:disable Metrics/LineLength
91
+ [{ 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' },
92
+ { file: 'bin/processors/image_uploader.rb', line: 21, function: 'bin.processors.image_uploader.block in make_streams' },
93
+ { 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' },
94
+ { file: '/tmp/jruby9022301782566983632extract/./META-INF/rails.rb', line: 13, function: 'tmp.jruby9022301782566983632extract.$dot.META_minus_INF.rails.file' }]
95
+ # rubocop:enable Metrics/LineLength
96
+ end
97
+
98
+ let(:ex) { JavaAirbrakeTestError.new.tap { |e| e.set_backtrace(backtrace) } }
99
+
100
+ it "returns a properly formatted array of hashes" do
101
+ allow(described_class).to receive(:java_exception?).and_return(true)
102
+ expect(described_class.parse(config, ex)).to eq(parsed_backtrace)
103
+ end
104
+ end
105
+
106
+ context "JRuby non-throwable exceptions" do
107
+ let(:backtrace) do
108
+ # rubocop:disable Metrics/LineLength
109
+ ['org.postgresql.core.v3.ConnectionFactoryImpl.openConnectionImpl(org/postgresql/core/v3/ConnectionFactoryImpl.java:257)',
110
+ 'org.postgresql.core.ConnectionFactory.openConnection(org/postgresql/core/ConnectionFactory.java:65)',
111
+ 'org.postgresql.jdbc2.AbstractJdbc2Connection.<init>(org/postgresql/jdbc2/AbstractJdbc2Connection.java:149)']
112
+ # rubocop:enable Metrics/LineLength
113
+ end
114
+
115
+ let(:parsed_backtrace) do
116
+ # rubocop:disable Metrics/LineLength
117
+ [{ file: 'org/postgresql/core/v3/ConnectionFactoryImpl.java', line: 257, function: 'org.postgresql.core.v3.ConnectionFactoryImpl.openConnectionImpl' },
118
+ { file: 'org/postgresql/core/ConnectionFactory.java', line: 65, function: 'org.postgresql.core.ConnectionFactory.openConnection' },
119
+ { file: 'org/postgresql/jdbc2/AbstractJdbc2Connection.java', line: 149, function: 'org.postgresql.jdbc2.AbstractJdbc2Connection.<init>' }]
120
+ # rubocop:enable Metrics/LineLength
121
+ end
122
+
123
+ let(:ex) { AirbrakeTestError.new.tap { |e| e.set_backtrace(backtrace) } }
124
+
125
+ it "returns a properly formatted array of hashes" do
126
+ expect(described_class.parse(config, ex)).to eq(parsed_backtrace)
127
+ end
128
+ end
129
+
130
+ context "generic backtrace" do
131
+ context "when function is absent" do
132
+ # rubocop:disable Metrics/LineLength
133
+ let(:generic_bt) do
134
+ ["/home/bingo/bango/assets/stylesheets/error_pages.scss:139:in `animation'",
135
+ "/home/bingo/bango/assets/stylesheets/error_pages.scss:139",
136
+ "/home/bingo/.gem/ruby/2.2.2/gems/sass-3.4.20/lib/sass/tree/visitors/perform.rb:349:in `block in visit_mixin'"]
137
+ end
138
+ # rubocop:enable Metrics/LineLength
139
+
140
+ let(:ex) { AirbrakeTestError.new.tap { |e| e.set_backtrace(generic_bt) } }
141
+
142
+ let(:parsed_backtrace) do
143
+ # rubocop:disable Metrics/LineLength, Style/HashSyntax, Layout/SpaceInsideHashLiteralBraces, Layout/SpaceAroundOperators
144
+ [{:file=>"/home/bingo/bango/assets/stylesheets/error_pages.scss", :line=>139, :function=>"animation"},
145
+ {:file=>"/home/bingo/bango/assets/stylesheets/error_pages.scss", :line=>139, :function=>nil},
146
+ {: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"}]
147
+ # rubocop:enable Metrics/LineLength, Style/HashSyntax, Layout/SpaceInsideHashLiteralBraces, Layout/SpaceAroundOperators
148
+ end
149
+
150
+ it "returns a properly formatted array of hashes" do
151
+ expect(described_class.parse(config, ex)).to eq(parsed_backtrace)
152
+ end
153
+ end
154
+
155
+ context "when line is absent" do
156
+ let(:generic_bt) do
157
+ ["/Users/grammakov/repositories/weintervene/config.ru:in `new'"]
158
+ end
159
+
160
+ let(:ex) { AirbrakeTestError.new.tap { |e| e.set_backtrace(generic_bt) } }
161
+
162
+ let(:parsed_backtrace) do
163
+ [{ file: '/Users/grammakov/repositories/weintervene/config.ru',
164
+ line: nil,
165
+ function: 'new' }]
166
+ end
167
+
168
+ it "returns a properly formatted array of hashes" do
169
+ expect(described_class.parse(config, ex)).to eq(parsed_backtrace)
170
+ end
171
+ end
172
+ end
173
+
174
+ context "unknown backtrace" do
175
+ let(:unknown_bt) { ['a b c 1 23 321 .rb'] }
176
+
177
+ let(:ex) { AirbrakeTestError.new.tap { |e| e.set_backtrace(unknown_bt) } }
178
+
179
+ it "returns array of hashes where each unknown frame is marked as 'function'" do
180
+ expect(
181
+ described_class.parse(config, ex)
182
+ ).to eq([file: nil, line: nil, function: 'a b c 1 23 321 .rb'])
183
+ end
184
+
185
+ it "logs unknown frames as errors" do
186
+ out = StringIO.new
187
+ config.logger = Logger.new(out)
188
+
189
+ expect { described_class.parse(config, ex) }.
190
+ to change { out.string }.
191
+ from('').
192
+ to(/ERROR -- : can't parse 'a b c 1 23 321 .rb'/)
193
+ end
194
+ end
195
+
196
+ context "given a backtrace with an empty function" do
197
+ let(:bt) do
198
+ ["/airbrake-ruby/vendor/jruby/1.9/gems/rspec-core-3.4.1/exe/rspec:3:in `'"]
199
+ end
200
+
201
+ let(:ex) { AirbrakeTestError.new.tap { |e| e.set_backtrace(bt) } }
202
+
203
+ let(:parsed_backtrace) do
204
+ [{ file: '/airbrake-ruby/vendor/jruby/1.9/gems/rspec-core-3.4.1/exe/rspec',
205
+ line: 3,
206
+ function: '' }]
207
+ end
208
+
209
+ it "returns a properly formatted array of hashes" do
210
+ expect(described_class.parse(config, ex)).to eq(parsed_backtrace)
211
+ end
212
+ end
213
+
214
+ context "given an Oracle backtrace" do
215
+ let(:bt) do
216
+ ['ORA-06512: at "STORE.LI_LICENSES_PACK", line 1945',
217
+ 'ORA-06512: at "ACTIVATION.LI_ACT_LICENSES_PACK", line 101',
218
+ 'ORA-06512: at line 2',
219
+ 'from stmt.c:243:in oci8lib_220.bundle']
220
+ end
221
+
222
+ let(:ex) { OCIError.new.tap { |e| e.set_backtrace(bt) } }
223
+
224
+ let(:parsed_backtrace) do
225
+ [{ file: nil, line: 1945, function: 'STORE.LI_LICENSES_PACK' },
226
+ { file: nil, line: 101, function: 'ACTIVATION.LI_ACT_LICENSES_PACK' },
227
+ { file: nil, line: 2, function: nil },
228
+ { file: 'stmt.c', line: 243, function: 'oci8lib_220.bundle' }]
229
+ end
230
+
231
+ it "returns a properly formatted array of hashes" do
232
+ stub_const('OCIError', AirbrakeTestError)
233
+ expect(described_class.parse(config, ex)).to eq(parsed_backtrace)
234
+ end
235
+ end
236
+
237
+ context "given an ExecJS exception" do
238
+ let(:bt) do
239
+ ['compile ((execjs):6692:19)',
240
+ 'eval (<anonymous>:1:10)',
241
+ '(execjs):6703:8',
242
+ 'require../helpers.exports ((execjs):1:102)',
243
+ 'Object.<anonymous> ((execjs):1:120)',
244
+ 'Object.Module._extensions..js (module.js:550:10)',
245
+ 'bootstrap_node.js:467:3',
246
+ "/opt/rubies/ruby-2.3.1/lib/ruby/2.3.0/benchmark.rb:308:in `realtime'"]
247
+ end
248
+
249
+ let(:ex) { ExecJS::RuntimeError.new.tap { |e| e.set_backtrace(bt) } }
250
+
251
+ let(:parsed_backtrace) do
252
+ [{ file: '(execjs)', line: 6692, function: 'compile' },
253
+ { file: '<anonymous>', line: 1, function: 'eval' },
254
+ { file: '(execjs)', line: 6703, function: '' },
255
+ { file: '(execjs)', line: 1, function: 'require../helpers.exports' },
256
+ { file: '(execjs)', line: 1, function: 'Object.<anonymous>' },
257
+ { file: 'module.js', line: 550, function: 'Object.Module._extensions..js' },
258
+ { file: 'bootstrap_node.js', line: 467, function: '' },
259
+ { file: '/opt/rubies/ruby-2.3.1/lib/ruby/2.3.0/benchmark.rb',
260
+ line: 308,
261
+ function: 'realtime' }]
262
+ end
263
+
264
+ it "returns a properly formatted array of hashes" do
265
+ stub_const('ExecJS::RuntimeError', AirbrakeTestError)
266
+ expect(described_class.parse(config, ex)).to eq(parsed_backtrace)
267
+ end
268
+ end
269
+
270
+ context "when code hunks are enabled" do
271
+ let(:config) do
272
+ config = Airbrake::Config.new
273
+ config.logger = Logger.new('/dev/null')
274
+ config.code_hunks = true
275
+ config
276
+ end
277
+
278
+ context "and when root_directory is configured" do
279
+ before { config.root_directory = project_root_path('') }
280
+
281
+ let(:parsed_backtrace) do
282
+ [
283
+ {
284
+ file: project_root_path('code.rb'),
285
+ line: 94,
286
+ function: 'to_json',
287
+ code: {
288
+ 92 => ' loop do',
289
+ 93 => ' begin',
290
+ 94 => ' json = @payload.to_json',
291
+ 95 => ' rescue *JSON_EXCEPTIONS => ex',
292
+ # rubocop:disable Metrics/LineLength,Lint/InterpolationCheck
293
+ 96 => ' @config.logger.debug("#{LOG_LABEL} `notice.to_json` failed: #{ex.class}: #{ex}")',
294
+ # rubocop:enable Metrics/LineLength,Lint/InterpolationCheck
295
+ }
296
+ },
297
+ {
298
+ file: fixture_path('notroot.txt'),
299
+ line: 3,
300
+ function: 'pineapple'
301
+ },
302
+ {
303
+ file: project_root_path('vendor/bundle/ignored_file.rb'),
304
+ line: 2,
305
+ function: 'ignore_me'
306
+ }
307
+ ]
308
+ end
309
+
310
+ it "attaches code to those frames files of which match root_directory" do
311
+ ex = RuntimeError.new
312
+ backtrace = [
313
+ project_root_path('code.rb') + ":94:in `to_json'",
314
+ fixture_path('notroot.txt' + ":3:in `pineapple'"),
315
+ project_root_path('vendor/bundle/ignored_file.rb') + ":2:in `ignore_me'"
316
+ ]
317
+ ex.set_backtrace(backtrace)
318
+ expect(described_class.parse(config, ex)).to eq(parsed_backtrace)
319
+ end
320
+ end
321
+
322
+ context "and when root_directory is a Pathname" do
323
+ before { config.root_directory = Pathname.new(project_root_path('')) }
324
+
325
+ let(:parsed_backtrace) do
326
+ [
327
+ {
328
+ file: project_root_path('code.rb'),
329
+ line: 94,
330
+ function: 'to_json',
331
+ code: {
332
+ 92 => ' loop do',
333
+ 93 => ' begin',
334
+ 94 => ' json = @payload.to_json',
335
+ 95 => ' rescue *JSON_EXCEPTIONS => ex',
336
+ # rubocop:disable Metrics/LineLength,Lint/InterpolationCheck
337
+ 96 => ' @config.logger.debug("#{LOG_LABEL} `notice.to_json` failed: #{ex.class}: #{ex}")',
338
+ # rubocop:enable Metrics/LineLength,Lint/InterpolationCheck
339
+ }
340
+ }
341
+ ]
342
+ end
343
+
344
+ it "attaches code to those frames files of which match root_directory" do
345
+ ex = RuntimeError.new
346
+ ex.set_backtrace([project_root_path('code.rb') + ":94:in `to_json'"])
347
+ expect(described_class.parse(config, ex)).to eq(parsed_backtrace)
348
+ end
349
+ end
350
+
351
+ context "and when root_directory isn't configured" do
352
+ before do
353
+ config.root_directory = nil
354
+ stub_const('Airbrake::Backtrace::CODE_FRAME_LIMIT', 2)
355
+ end
356
+
357
+ let(:parsed_backtrace) do
358
+ [
359
+ {
360
+ file: project_root_path('code.rb'),
361
+ line: 94,
362
+ function: 'to_json',
363
+ code: {
364
+ 92 => ' loop do',
365
+ 93 => ' begin',
366
+ 94 => ' json = @payload.to_json',
367
+ 95 => ' rescue *JSON_EXCEPTIONS => ex',
368
+ # rubocop:disable Metrics/LineLength,Lint/InterpolationCheck
369
+ 96 => ' @config.logger.debug("#{LOG_LABEL} `notice.to_json` failed: #{ex.class}: #{ex}")',
370
+ # rubocop:enable Metrics/LineLength,Lint/InterpolationCheck
371
+ }
372
+ },
373
+ {
374
+ file: project_root_path('code.rb'),
375
+ line: 95,
376
+ function: 'to_json',
377
+ code: {
378
+ 93 => ' begin',
379
+ 94 => ' json = @payload.to_json',
380
+ 95 => ' rescue *JSON_EXCEPTIONS => ex',
381
+ # rubocop:disable Metrics/LineLength,Lint/InterpolationCheck
382
+ 96 => ' @config.logger.debug("#{LOG_LABEL} `notice.to_json` failed: #{ex.class}: #{ex}")',
383
+ # rubocop:enable Metrics/LineLength,Lint/InterpolationCheck
384
+ 97 => ' else'
385
+ }
386
+ },
387
+ {
388
+ file: project_root_path('code.rb'),
389
+ line: 96,
390
+ function: 'to_json'
391
+ }
392
+ ]
393
+ end
394
+
395
+ it "attaches code to first N frames" do
396
+ ex = RuntimeError.new
397
+ backtrace = [
398
+ project_root_path('code.rb') + ":94:in `to_json'",
399
+ project_root_path('code.rb') + ":95:in `to_json'",
400
+ project_root_path('code.rb') + ":96:in `to_json'"
401
+ ]
402
+ ex.set_backtrace(backtrace)
403
+ expect(described_class.parse(config, ex)).to eq(parsed_backtrace)
404
+ end
405
+ end
406
+ end
407
+
408
+ context "when code hunks are disabled" do
409
+ let(:config) do
410
+ config = Airbrake::Config.new
411
+ config.logger = Logger.new('/dev/null')
412
+ config.code_hunks = false
413
+ config
414
+ end
415
+
416
+ context "and when root_directory is configured" do
417
+ before { config.root_directory = project_root_path('') }
418
+
419
+ let(:parsed_backtrace) do
420
+ [
421
+ {
422
+ file: project_root_path('code.rb'),
423
+ line: 94,
424
+ function: 'to_json'
425
+ }
426
+ ]
427
+ end
428
+
429
+ it "doesn't attach code to frames" do
430
+ ex = RuntimeError.new
431
+ backtrace = [project_root_path('code.rb') + ":94:in `to_json'"]
432
+ ex.set_backtrace(backtrace)
433
+ expect(described_class.parse(config, ex)).to eq(parsed_backtrace)
434
+ end
435
+ end
436
+ end
437
+ end
438
+ end