airbrake-ruby 3.2.2-java

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