bugsnag 6.12.0 → 6.14.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (126) hide show
  1. checksums.yaml +4 -4
  2. data/.buildkite/pipeline.yml +470 -0
  3. data/.rubocop.yml +55 -0
  4. data/.rubocop_todo.yml +530 -160
  5. data/CHANGELOG.md +67 -0
  6. data/CONTRIBUTING.md +1 -9
  7. data/Gemfile +14 -7
  8. data/TESTING.md +81 -0
  9. data/VERSION +1 -1
  10. data/docker-compose.yml +46 -0
  11. data/dockerfiles/Dockerfile.jruby-unit-tests +13 -0
  12. data/dockerfiles/Dockerfile.ruby-maze-runner +26 -0
  13. data/dockerfiles/Dockerfile.ruby-unit-tests +12 -0
  14. data/features/delayed_job.feature +6 -22
  15. data/features/fixtures/delayed_job/Dockerfile +2 -4
  16. data/features/fixtures/delayed_job/app/Gemfile +1 -1
  17. data/features/fixtures/delayed_job/app/Rakefile +18 -0
  18. data/features/fixtures/docker-compose.yml +28 -40
  19. data/features/fixtures/expected_breadcrumbs/active_job.json +9 -0
  20. data/features/fixtures/expected_breadcrumbs/mongo_failed.json +15 -0
  21. data/features/fixtures/expected_breadcrumbs/mongo_filtered_request.json +15 -0
  22. data/features/fixtures/expected_breadcrumbs/mongo_filtered_result.json +15 -0
  23. data/features/fixtures/expected_breadcrumbs/mongo_success.json +14 -0
  24. data/features/fixtures/expected_breadcrumbs/request.json +13 -0
  25. data/features/fixtures/expected_breadcrumbs/sql_with_bindings.json +12 -0
  26. data/features/fixtures/expected_breadcrumbs/sql_without_bindings.json +11 -0
  27. data/features/fixtures/plain/Dockerfile +2 -2
  28. data/features/fixtures/plain/app/app.rb +1 -3
  29. data/features/fixtures/plain/app/delivery/fork_threadpool.rb +3 -1
  30. data/features/fixtures/plain/app/unhandled/{Interrupt.rb → interrupt.rb} +0 -0
  31. data/features/fixtures/rack1/Dockerfile +2 -2
  32. data/features/fixtures/rack2/Dockerfile +2 -2
  33. data/features/fixtures/rails3/Dockerfile +2 -2
  34. data/features/fixtures/rails3/app/Gemfile +4 -0
  35. data/features/fixtures/rails3/app/config/initializers/bugsnag.rb +3 -2
  36. data/features/fixtures/rails4/Dockerfile +2 -5
  37. data/features/fixtures/rails4/app/config/initializers/bugsnag.rb +2 -1
  38. data/features/fixtures/rails5/Dockerfile +2 -2
  39. data/features/fixtures/rails5/app/Gemfile +3 -2
  40. data/features/fixtures/rails5/app/config/initializers/bugsnag.rb +2 -1
  41. data/features/fixtures/rails6/Dockerfile +2 -2
  42. data/features/fixtures/rails6/app/Gemfile +3 -2
  43. data/features/fixtures/rails6/app/app/controllers/mongo_controller.rb +22 -0
  44. data/features/fixtures/rails6/app/app/models/mongo_model.rb +6 -0
  45. data/features/fixtures/rails6/app/config/environments/development.rb +2 -0
  46. data/features/fixtures/rails6/app/config/environments/production.rb +1 -0
  47. data/features/fixtures/rails6/app/config/environments/rails_env.rb +1 -0
  48. data/features/fixtures/rails6/app/config/environments/test.rb +1 -0
  49. data/features/fixtures/rails6/app/config/initializers/bugsnag.rb +2 -1
  50. data/features/fixtures/rails6/app/config/mongoid.yml +23 -0
  51. data/features/fixtures/rails6/app/config/routes.rb +4 -0
  52. data/features/fixtures/resque/Dockerfile +2 -2
  53. data/features/fixtures/sidekiq/Dockerfile +5 -7
  54. data/features/fixtures/sidekiq/app/Gemfile +2 -1
  55. data/features/fixtures/sidekiq/app/Rakefile.rb +14 -0
  56. data/features/fixtures/sinatra1/Dockerfile +2 -2
  57. data/features/fixtures/sinatra2/Dockerfile +2 -2
  58. data/features/plain_features/add_tab.feature +24 -97
  59. data/features/plain_features/app_type.feature +6 -25
  60. data/features/plain_features/app_version.feature +6 -25
  61. data/features/plain_features/auto_notify.feature +4 -20
  62. data/features/plain_features/delivery.feature +12 -60
  63. data/features/plain_features/exception_data.feature +24 -94
  64. data/features/plain_features/filters.feature +9 -43
  65. data/features/plain_features/handled_errors.feature +16 -78
  66. data/features/plain_features/ignore_classes.feature +5 -23
  67. data/features/plain_features/ignore_report.feature +6 -24
  68. data/features/plain_features/proxies.feature +13 -56
  69. data/features/plain_features/release_stages.feature +9 -40
  70. data/features/plain_features/report_api_key.feature +9 -35
  71. data/features/plain_features/report_severity.feature +8 -35
  72. data/features/plain_features/report_stack_frames.feature +24 -92
  73. data/features/plain_features/report_user.feature +23 -96
  74. data/features/plain_features/unhandled_errors.feature +17 -88
  75. data/features/rails_features/api_key.feature +12 -58
  76. data/features/rails_features/app_type.feature +13 -58
  77. data/features/rails_features/app_version.feature +19 -80
  78. data/features/rails_features/auto_capture_sessions.feature +31 -112
  79. data/features/rails_features/auto_notify.feature +28 -105
  80. data/features/rails_features/before_notify.feature +18 -83
  81. data/features/rails_features/breadcrumbs.feature +40 -137
  82. data/features/rails_features/handled.feature +18 -82
  83. data/features/rails_features/ignore_classes.feature +12 -51
  84. data/features/rails_features/meta_data_filters.feature +9 -33
  85. data/features/rails_features/mongo_breadcrumbs.feature +22 -96
  86. data/features/rails_features/project_root.feature +19 -84
  87. data/features/rails_features/release_stage.feature +20 -82
  88. data/features/rails_features/send_code.feature +13 -55
  89. data/features/rails_features/send_environment.feature +7 -33
  90. data/features/rails_features/unhandled.feature +6 -31
  91. data/features/rails_features/user_info.feature +27 -65
  92. data/features/sidekiq.feature +12 -79
  93. data/features/steps/ruby_notifier_steps.rb +59 -15
  94. data/features/support/env.rb +12 -45
  95. data/lib/bugsnag.rb +74 -21
  96. data/lib/bugsnag/breadcrumbs/breadcrumbs.rb +0 -2
  97. data/lib/bugsnag/breadcrumbs/validator.rb +0 -6
  98. data/lib/bugsnag/cleaner.rb +129 -60
  99. data/lib/bugsnag/configuration.rb +31 -2
  100. data/lib/bugsnag/helpers.rb +2 -4
  101. data/lib/bugsnag/integrations/que.rb +7 -4
  102. data/lib/bugsnag/integrations/railtie.rb +1 -1
  103. data/lib/bugsnag/middleware/discard_error_class.rb +30 -0
  104. data/lib/bugsnag/middleware/exception_meta_data.rb +15 -9
  105. data/lib/bugsnag/middleware/ignore_error_class.rb +2 -0
  106. data/lib/bugsnag/middleware/rack_request.rb +2 -4
  107. data/lib/bugsnag/report.rb +3 -13
  108. data/lib/bugsnag/stacktrace.rb +6 -10
  109. data/spec/breadcrumbs/breadcrumb_spec.rb +1 -1
  110. data/spec/breadcrumbs/validator_spec.rb +1 -26
  111. data/spec/bugsnag_spec.rb +2 -2
  112. data/spec/cleaner_spec.rb +202 -10
  113. data/spec/configuration_spec.rb +16 -1
  114. data/spec/fixtures/apps/rails-initializer-config/Gemfile +5 -1
  115. data/spec/fixtures/apps/rails-invalid-initializer-config/Gemfile +5 -1
  116. data/spec/fixtures/apps/rails-no-config/Gemfile +5 -1
  117. data/spec/helper_spec.rb +0 -31
  118. data/spec/integrations/logger_spec.rb +1 -1
  119. data/spec/integrations/rack_spec.rb +8 -6
  120. data/spec/integrations/rake_spec.rb +1 -1
  121. data/spec/report_spec.rb +324 -26
  122. data/spec/spec_helper.rb +6 -1
  123. data/spec/stacktrace_spec.rb +179 -72
  124. metadata +23 -7
  125. data/.travis.yml +0 -117
  126. data/features/plain_features/api_key.feature +0 -25
@@ -332,10 +332,14 @@ describe Bugsnag::Configuration do
332
332
  end
333
333
  end
334
334
 
335
- it "should have exit exception classes ignored by default" do
335
+ it "should have exit exception classes in ignore_classes by default" do
336
336
  expect(subject.ignore_classes).to eq(Set.new([SystemExit, SignalException]))
337
337
  end
338
338
 
339
+ it "should have nothing in discard_classes by default" do
340
+ expect(subject.discard_classes).to eq(Set.new([]))
341
+ end
342
+
339
343
  describe "#breadcrumbs" do
340
344
  it "first returns a new circular buffer" do
341
345
  buffer = subject.breadcrumbs
@@ -427,4 +431,15 @@ describe Bugsnag::Configuration do
427
431
  expect(second_array).to eq([1, 2])
428
432
  end
429
433
  end
434
+
435
+ describe "#vendor_path" do
436
+ it "returns the default vendor path" do
437
+ expect(subject.vendor_path).to eq(Bugsnag::Configuration::DEFAULT_VENDOR_PATH)
438
+ end
439
+
440
+ it "returns the defined vendor path" do
441
+ subject.vendor_path = /foo/
442
+ expect(subject.vendor_path).to eq(/foo/)
443
+ end
444
+ end
430
445
  end
@@ -1,5 +1,9 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
- gem 'railties', '4.2.10', require: %w(action_controller rails)
3
+ ruby_version = Gem::Version.new(RUBY_VERSION.dup)
4
+
5
+ gem 'railties', ruby_version <= Gem::Version.new('2.6') ? '4.2.10' : '~> 6.0.2', require: %w(action_controller rails)
6
+ gem 'rake', ruby_version <= Gem::Version.new('1.9.3') ? '~> 11.3.0' : '~> 12.3.0'
7
+ gem 'minitest', ruby_version <= Gem::Version.new('2.2') ? '5.11.3' : '~> 5.13.0'
4
8
  gem 'nokogiri', '1.6.8'
5
9
  gem 'bugsnag', path: '../../../..'
@@ -1,5 +1,9 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
- gem 'railties', '4.2.10', require: %w(action_controller rails)
3
+ ruby_version = Gem::Version.new(RUBY_VERSION.dup)
4
+
5
+ gem 'railties', ruby_version <= Gem::Version.new('2.6') ? '4.2.10' : '~> 6.0.2', require: %w(action_controller rails)
6
+ gem 'rake', ruby_version <= Gem::Version.new('1.9.3') ? '~> 11.3.0' : '~> 12.3.0'
7
+ gem 'minitest', ruby_version <= Gem::Version.new('2.2') ? '5.11.3' : '~> 5.13.0'
4
8
  gem 'nokogiri', '1.6.8'
5
9
  gem 'bugsnag', path: '../../../..'
@@ -1,5 +1,9 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
- gem 'railties', '4.2.10', require: %w(action_controller rails)
3
+ ruby_version = Gem::Version.new(RUBY_VERSION.dup)
4
+
5
+ gem 'railties', ruby_version <= Gem::Version.new('2.6') ? '4.2.10' : '~> 6.0.2', require: %w(action_controller rails)
6
+ gem 'rake', ruby_version <= Gem::Version.new('1.9.3') ? '~> 11.3.0' : '~> 12.3.0'
7
+ gem 'minitest', ruby_version <= Gem::Version.new('2.2') ? '5.11.3' : '~> 5.13.0'
4
8
  gem 'nokogiri', '1.6.8'
5
9
  gem 'bugsnag', path: '../../../..'
@@ -4,23 +4,7 @@ require 'spec_helper'
4
4
  require 'set'
5
5
 
6
6
  describe Bugsnag::Helpers do
7
-
8
7
  describe "trim_if_needed" do
9
-
10
- it "breaks recursion" do
11
- a = [1, 2, 3]
12
- b = [2, a]
13
- a << b
14
- value = Bugsnag::Helpers.trim_if_needed(a)
15
- expect(value).to eq([1, 2, 3, [2, "[RECURSION]"]])
16
- end
17
-
18
- it "does not break equal objects without recursion" do
19
- data = [1, [1, 2], [1, 2], "a"]
20
- value = Bugsnag::Helpers.trim_if_needed(data)
21
- expect(value).to eq data
22
- end
23
-
24
8
  it "preserves bool types" do
25
9
  value = Bugsnag::Helpers.trim_if_needed([1, 3, true, "NO", "2", false])
26
10
  expect(value[2]).to be_a(TrueClass)
@@ -39,21 +23,7 @@ describe Bugsnag::Helpers do
39
23
  expect(value[4]).to be_a(String)
40
24
  end
41
25
 
42
- context "an object will throw if `to_s` is called" do
43
- class StringRaiser
44
- def to_s
45
- raise 'Oh no you do not!'
46
- end
47
- end
48
-
49
- it "uses the string '[RAISED]' instead" do
50
- value = Bugsnag::Helpers.trim_if_needed([1, 3, StringRaiser.new])
51
- expect(value[2]).to eq "[RAISED]"
52
- end
53
- end
54
-
55
26
  context "payload length is less than allowed" do
56
-
57
27
  it "does not change strings" do
58
28
  value = SecureRandom.hex(4096)
59
29
  expect(Bugsnag::Helpers.trim_if_needed(value)).to eq value
@@ -71,7 +41,6 @@ describe Bugsnag::Helpers do
71
41
  end
72
42
 
73
43
  context "payload length is greater than allowed" do
74
-
75
44
  it "trims metadata strings" do
76
45
  payload = {
77
46
  :events => [{
@@ -61,7 +61,7 @@ describe 'Configuration.logger' do
61
61
  end
62
62
  end
63
63
 
64
- context 'sets an invalid API key using the BUGSNAG_API_KEY env var' do
64
+ context 'when the API key is invalid in the bugsnag initializer' do
65
65
  it 'logs a warning' do
66
66
  skip "Incompatible with Ruby <2.0 and JRuby" if incompatible
67
67
  output = run_app('rails-invalid-initializer-config')
@@ -127,9 +127,10 @@ describe Bugsnag::Rack do
127
127
  expect(report).to receive(:context=).with("TEST /TEST_PATH")
128
128
  expect(report).to receive(:user).and_return({})
129
129
 
130
- config = double
131
- allow(config).to receive(:send_environment).and_return(true)
132
- allow(config).to receive(:meta_data_filters).and_return(['email'])
130
+ config = Bugsnag.configuration
131
+ config.send_environment = true
132
+ config.meta_data_filters = ['email']
133
+
133
134
  allow(report).to receive(:configuration).and_return(config)
134
135
  expect(report).to receive(:add_tab).once.with(:request, {
135
136
  :url => "http://test_host/TEST_PATH?email=[FILTERED]&another_param=thing",
@@ -187,9 +188,10 @@ describe Bugsnag::Rack do
187
188
  expect(report).to receive(:context=).with("TEST /TEST_PATH")
188
189
  expect(report).to receive(:user).and_return({})
189
190
 
190
- config = double
191
- allow(config).to receive(:send_environment).and_return(true)
192
- allow(config).to receive(:meta_data_filters).and_return(nil)
191
+ config = Bugsnag.configuration
192
+ config.send_environment = true
193
+ config.meta_data_filters = []
194
+
193
195
  allow(report).to receive(:configuration).and_return(config)
194
196
  expect(report).to receive(:add_tab).once.with(:environment, rack_env)
195
197
  expect(report).to receive(:add_tab).once.with(:request, {
@@ -58,7 +58,7 @@ describe "Bugsnag Rake integration" do
58
58
  ENV['BUGSNAG_TEST_SERVER_PORT'] = server.config[:Port].to_s
59
59
  task_fixtures_path = File.join(File.dirname(__FILE__), '../fixtures', 'tasks')
60
60
  Dir.chdir(task_fixtures_path) do
61
- system("bundle exec rake test:crash > /dev/null 2>&1")
61
+ system("../../../bin/rake test:crash")
62
62
  end
63
63
 
64
64
  result = request()
@@ -1,5 +1,5 @@
1
1
  # encoding: utf-8
2
- require 'spec_helper'
2
+ require_relative './spec_helper'
3
3
  require 'securerandom'
4
4
  require 'ostruct'
5
5
 
@@ -27,6 +27,7 @@ class JRubyException
27
27
  end
28
28
  end
29
29
 
30
+ # rubocop:disable Metrics/BlockLength
30
31
  describe Bugsnag::Report do
31
32
  it "should contain an api_key if one is set" do
32
33
  Bugsnag.notify(BugsnagTestException.new("It crashed"))
@@ -149,10 +150,12 @@ describe Bugsnag::Report do
149
150
  original_ignore_classes = Bugsnag.configuration.ignore_classes
150
151
 
151
152
  begin
152
- # The default ignore_classes includes SignalException, so we need to
153
+ # The default ignore classes includes SignalException, so we need to
153
154
  # temporarily set it to something else.
154
155
  Bugsnag.configuration.ignore_classes = Set[SystemExit]
156
+
155
157
  Bugsnag.notify(SignalException.new("TERM"))
158
+
156
159
  expect(Bugsnag).to have_sent_notification{ |payload, headers|
157
160
  event = get_event_from_payload(payload)
158
161
  expect(event["unhandled"]).to be false
@@ -589,7 +592,6 @@ describe Bugsnag::Report do
589
592
  end
590
593
 
591
594
  it "filters params from all payload hashes if they are set in default meta_data_filters" do
592
-
593
595
  Bugsnag.notify(BugsnagTestException.new("It crashed")) do |report|
594
596
  report.meta_data.merge!({
595
597
  :request => {
@@ -635,7 +637,6 @@ describe Bugsnag::Report do
635
637
  end
636
638
 
637
639
  it "filters params from all payload hashes if they are added to meta_data_filters" do
638
-
639
640
  Bugsnag.configuration.meta_data_filters << "other_data"
640
641
  Bugsnag.notify(BugsnagTestException.new("It crashed")) do |report|
641
642
  report.meta_data.merge!({:request => {:params => {:password => "1234", :other_password => "123456", :other_data => "123456"}}})
@@ -653,7 +654,6 @@ describe Bugsnag::Report do
653
654
  end
654
655
 
655
656
  it "filters params from all payload hashes if they are added to meta_data_filters as regex" do
656
-
657
657
  Bugsnag.configuration.meta_data_filters << /other_data/
658
658
  Bugsnag.notify(BugsnagTestException.new("It crashed")) do |report|
659
659
  report.meta_data.merge!({:request => {:params => {:password => "1234", :other_password => "123456", :other_data => "123456"}}})
@@ -671,7 +671,6 @@ describe Bugsnag::Report do
671
671
  end
672
672
 
673
673
  it "filters params from all payload hashes if they are added to meta_data_filters as partial regex" do
674
-
675
674
  Bugsnag.configuration.meta_data_filters << /r_data/
676
675
  Bugsnag.notify(BugsnagTestException.new("It crashed")) do |report|
677
676
  report.meta_data.merge!({:request => {:params => {:password => "1234", :other_password => "123456", :other_data => "123456"}}})
@@ -702,6 +701,33 @@ describe Bugsnag::Report do
702
701
  }
703
702
  end
704
703
 
704
+ it "does not apply filters outside of report.meta_data" do
705
+ Bugsnag.configuration.meta_data_filters << "data"
706
+
707
+ Bugsnag.notify(BugsnagTestException.new("It crashed")) do |report|
708
+ report.meta_data = {
709
+ xyz: "abc",
710
+ data: "123456"
711
+ }
712
+
713
+ report.user = {
714
+ id: 123,
715
+ data: "hello"
716
+ }
717
+ end
718
+
719
+ expect(Bugsnag).to have_sent_notification{ |payload, headers|
720
+ event = get_event_from_payload(payload)
721
+
722
+ expect(event["metaData"]).not_to be_nil
723
+ expect(event["metaData"]["xyz"]).to eq("abc")
724
+ expect(event["metaData"]["data"]).to eq("[FILTERED]")
725
+
726
+ expect(event["user"]).not_to be_nil
727
+ expect(event["user"]["data"]).to eq("hello")
728
+ }
729
+ end
730
+
705
731
  it "does not notify if report ignored" do
706
732
  Bugsnag.notify(BugsnagTestException.new("It crashed")) do |report|
707
733
  report.ignore!
@@ -710,38 +736,154 @@ describe Bugsnag::Report do
710
736
  expect(Bugsnag).not_to have_sent_notification
711
737
  end
712
738
 
713
- it "does not notify if the exception class is in the default ignore_classes list" do
714
- Bugsnag.configuration.ignore_classes << ActiveRecord::RecordNotFound
715
- Bugsnag.notify(ActiveRecord::RecordNotFound.new("It crashed"))
739
+ context "ignore_classes" do
740
+ context "as a constant" do
741
+ it "ignores exception when its class is ignored" do
742
+ Bugsnag.configuration.ignore_classes << BugsnagTestException
716
743
 
717
- expect(Bugsnag).not_to have_sent_notification
718
- end
744
+ Bugsnag.notify(BugsnagTestException.new("It crashed"))
719
745
 
720
- it "does not notify if the non-default exception class is added to the ignore_classes" do
721
- Bugsnag.configuration.ignore_classes << BugsnagTestException
746
+ expect(Bugsnag).not_to have_sent_notification
747
+ end
722
748
 
723
- Bugsnag.notify(BugsnagTestException.new("It crashed"))
749
+ it "ignores exception when its ancestor is ignored" do
750
+ Bugsnag.configuration.ignore_classes << BugsnagTestException
724
751
 
725
- expect(Bugsnag).not_to have_sent_notification
726
- end
752
+ Bugsnag.notify(BugsnagSubclassTestException.new("It crashed"))
727
753
 
728
- it "does not notify if exception's ancestor is an ignored class" do
729
- Bugsnag.configuration.ignore_classes << BugsnagTestException
754
+ expect(Bugsnag).not_to have_sent_notification
755
+ end
730
756
 
731
- Bugsnag.notify(BugsnagSubclassTestException.new("It crashed"))
757
+ it "ignores exception when the original exception is ignored" do
758
+ Bugsnag.configuration.ignore_classes << BugsnagTestException
732
759
 
733
- expect(Bugsnag).not_to have_sent_notification
760
+ ex = NestedException.new("Self-referential exception")
761
+ ex.original_exception = BugsnagTestException.new("It crashed")
762
+
763
+ Bugsnag.notify(ex)
764
+
765
+ expect(Bugsnag).not_to have_sent_notification
766
+ end
767
+ end
768
+
769
+ context "as a proc" do
770
+ it "ignores exception when the proc returns true" do
771
+ Bugsnag.configuration.ignore_classes << ->(exception) { true }
772
+
773
+ Bugsnag.notify(BugsnagTestException.new("It crashed"))
774
+
775
+ expect(Bugsnag).not_to have_sent_notification
776
+ end
777
+
778
+ it "does not ignore exception when proc returns false" do
779
+ Bugsnag.configuration.ignore_classes << ->(exception) { false }
780
+
781
+ Bugsnag.notify(BugsnagTestException.new("It crashed"))
782
+
783
+ expect(Bugsnag).to have_sent_notification { |payload, headers|
784
+ exception = get_exception_from_payload(payload)
785
+
786
+ expect(exception["errorClass"]).to eq("BugsnagTestException")
787
+ expect(exception["message"]).to eq("It crashed")
788
+ }
789
+ end
790
+ end
734
791
  end
735
792
 
736
- it "does not notify if any caused exception is an ignored class" do
737
- Bugsnag.configuration.ignore_classes << NestedException
793
+ context "discard_classes" do
794
+ context "as a string" do
795
+ it "discards exception when its class should be discarded" do
796
+ Bugsnag.configuration.discard_classes << "BugsnagTestException"
738
797
 
739
- ex = NestedException.new("Self-referential exception")
740
- ex.original_exception = BugsnagTestException.new("It crashed")
798
+ Bugsnag.notify(BugsnagTestException.new("It crashed"))
741
799
 
742
- Bugsnag.notify(ex)
800
+ expect(Bugsnag).not_to have_sent_notification
801
+ end
743
802
 
744
- expect(Bugsnag).not_to have_sent_notification
803
+ it "discards exception when the original exception should be discarded" do
804
+ Bugsnag.configuration.discard_classes << "BugsnagTestException"
805
+
806
+ ex = NestedException.new("Self-referential exception")
807
+ ex.original_exception = BugsnagTestException.new("It crashed")
808
+
809
+ Bugsnag.notify(ex)
810
+
811
+ expect(Bugsnag).not_to have_sent_notification
812
+ end
813
+
814
+ it "does not discard exception with a typo" do
815
+ Bugsnag.configuration.discard_classes << "BugsnagToastException"
816
+
817
+ Bugsnag.notify(BugsnagTestException.new("It crashed"))
818
+
819
+ expect(Bugsnag).to have_sent_notification { |payload, headers|
820
+ exception = get_exception_from_payload(payload)
821
+
822
+ expect(exception["errorClass"]).to eq("BugsnagTestException")
823
+ expect(exception["message"]).to eq("It crashed")
824
+ }
825
+ end
826
+
827
+ it "does not discard exception when its ancestor is discarded" do
828
+ Bugsnag.configuration.discard_classes << "BugsnagTestException"
829
+
830
+ Bugsnag.notify(BugsnagSubclassTestException.new("It crashed"))
831
+
832
+ expect(Bugsnag).to have_sent_notification { |payload, headers|
833
+ exception = get_exception_from_payload(payload)
834
+
835
+ expect(exception["errorClass"]).to eq("BugsnagSubclassTestException")
836
+ expect(exception["message"]).to eq("It crashed")
837
+ }
838
+ end
839
+ end
840
+
841
+ context "as a regexp" do
842
+ it "discards exception when its class should be discarded" do
843
+ Bugsnag.configuration.discard_classes << /^BugsnagTest.*/
844
+
845
+ Bugsnag.notify(BugsnagTestException.new("It crashed"))
846
+
847
+ expect(Bugsnag).not_to have_sent_notification
848
+ end
849
+
850
+ it "discards exception when the original exception should be discarded" do
851
+ Bugsnag.configuration.discard_classes << /^BugsnagTest.*/
852
+
853
+ ex = NestedException.new("Self-referential exception")
854
+ ex.original_exception = BugsnagTestException.new("It crashed")
855
+
856
+ Bugsnag.notify(ex)
857
+
858
+ expect(Bugsnag).not_to have_sent_notification
859
+ end
860
+
861
+ it "does not discard exception when regexp does not match" do
862
+ Bugsnag.configuration.discard_classes << /^NotBugsnag.*/
863
+
864
+ Bugsnag.notify(BugsnagTestException.new("It crashed"))
865
+
866
+ expect(Bugsnag).to have_sent_notification { |payload, headers|
867
+ exception = get_exception_from_payload(payload)
868
+
869
+ expect(exception["errorClass"]).to eq("BugsnagTestException")
870
+ expect(exception["message"]).to eq("It crashed")
871
+ }
872
+ end
873
+
874
+ it "does not discard exception when its ancestor is discarded" do
875
+ Bugsnag.configuration.discard_classes << /^BugsnagTest.*/
876
+
877
+ Bugsnag.notify(BugsnagSubclassTestException.new("It crashed"))
878
+
879
+ expect(Bugsnag).to have_sent_notification { |payload, headers|
880
+ exception = get_exception_from_payload(payload)
881
+
882
+ expect(exception["errorClass"]).to eq("BugsnagSubclassTestException")
883
+ expect(exception["message"]).to eq("It crashed")
884
+ }
885
+ end
886
+ end
745
887
  end
746
888
 
747
889
  it "sends the cause of the exception" do
@@ -989,6 +1131,161 @@ describe Bugsnag::Report do
989
1131
  }
990
1132
  end
991
1133
 
1134
+ it "should handle recursive metadata" do
1135
+ a = [1, 2, 3]
1136
+ b = [2, a]
1137
+ a << b
1138
+ c = [1, 2, 3]
1139
+
1140
+ Bugsnag.notify(BugsnagTestException.new("It crashed")) do |report|
1141
+ report.add_tab(:some_tab, {
1142
+ a: a,
1143
+ b: b,
1144
+ c: c
1145
+ })
1146
+ end
1147
+
1148
+ expect(Bugsnag).to have_sent_notification{ |payload, headers|
1149
+ event = get_event_from_payload(payload)
1150
+ expect(event["metaData"]["some_tab"]).to eq({
1151
+ "a" => [1, 2, 3, [2, "[RECURSION]"]],
1152
+ "b" => [2, "[RECURSION]"],
1153
+ "c" => [1, 2, 3]
1154
+ })
1155
+ }
1156
+ end
1157
+
1158
+ it "does not detect two equal objects as recursion" do
1159
+ Bugsnag.notify(BugsnagTestException.new("It crashed")) do |report|
1160
+ report.add_tab(:some_tab, {
1161
+ data: [1, [1, 2], [1, 2], "a"]
1162
+ })
1163
+ end
1164
+
1165
+ expect(Bugsnag).to have_sent_notification{ |payload, headers|
1166
+ event = get_event_from_payload(payload)
1167
+ expect(event["metaData"]["some_tab"]).to eq({
1168
+ "data" => [1, [1, 2], [1, 2], "a"]
1169
+ })
1170
+ }
1171
+ end
1172
+
1173
+ context "an object that throws if `to_s` is called" do
1174
+ class StringRaiser
1175
+ def to_s
1176
+ raise 'Oh no you do not!'
1177
+ end
1178
+ end
1179
+
1180
+ it "uses the string '[RAISED]' instead" do
1181
+ Bugsnag.notify(BugsnagTestException.new("It crashed")) do |report|
1182
+ report.add_tab(:some_tab, {
1183
+ data: [1, 2, StringRaiser.new]
1184
+ })
1185
+ end
1186
+
1187
+ expect(Bugsnag).to have_sent_notification{ |payload, headers|
1188
+ event = get_event_from_payload(payload)
1189
+ expect(event["metaData"]["some_tab"]).to eq({
1190
+ "data" => [1, 2, "[RAISED]"]
1191
+ })
1192
+ }
1193
+ end
1194
+
1195
+ it "replaces hash key with '[RAISED]'" do
1196
+ Bugsnag.notify(BugsnagTestException.new("It crashed")) do |report|
1197
+ report.add_tab(:some_tab, {
1198
+ StringRaiser.new => 1
1199
+ })
1200
+ end
1201
+
1202
+ expect(Bugsnag).to have_sent_notification{ |payload, headers|
1203
+ event = get_event_from_payload(payload)
1204
+ expect(event["metaData"]["some_tab"]).to eq({
1205
+ "[RAISED]" => "[FILTERED]"
1206
+ })
1207
+ }
1208
+ end
1209
+
1210
+ it "uses a single '[RAISED]'key when multiple keys raise" do
1211
+ Bugsnag.notify(BugsnagTestException.new("It crashed")) do |report|
1212
+ report.add_tab(:some_tab, {
1213
+ StringRaiser.new => 1,
1214
+ StringRaiser.new => 2
1215
+ })
1216
+ end
1217
+
1218
+ expect(Bugsnag).to have_sent_notification{ |payload, headers|
1219
+ event = get_event_from_payload(payload)
1220
+ expect(event["metaData"]["some_tab"]).to eq({
1221
+ "[RAISED]" => "[FILTERED]"
1222
+ })
1223
+ }
1224
+ end
1225
+ end
1226
+
1227
+ context "an object that infinitely recurse if `to_s` is called" do
1228
+ is_jruby = defined?(RUBY_ENGINE) && RUBY_ENGINE == 'jruby'
1229
+
1230
+ class StringRecurser
1231
+ def to_s
1232
+ to_s
1233
+ end
1234
+ end
1235
+
1236
+ it "uses the string '[RECURSION]' instead" do
1237
+ skip "JRuby doesn't allow recovery from SystemStackErrors" if is_jruby
1238
+
1239
+ Bugsnag.notify(BugsnagTestException.new("It crashed")) do |report|
1240
+ report.add_tab(:some_tab, {
1241
+ data: [1, 2, StringRecurser.new]
1242
+ })
1243
+ end
1244
+
1245
+ expect(Bugsnag).to have_sent_notification{ |payload, headers|
1246
+ event = get_event_from_payload(payload)
1247
+ expect(event["metaData"]["some_tab"]).to eq({
1248
+ "data" => [1, 2, "[RECURSION]"]
1249
+ })
1250
+ }
1251
+ end
1252
+
1253
+ it "replaces hash key with '[RECURSION]'" do
1254
+ skip "JRuby doesn't allow recovery from SystemStackErrors" if is_jruby
1255
+
1256
+ Bugsnag.notify(BugsnagTestException.new("It crashed")) do |report|
1257
+ report.add_tab(:some_tab, {
1258
+ StringRecurser.new => 1
1259
+ })
1260
+ end
1261
+
1262
+ expect(Bugsnag).to have_sent_notification{ |payload, headers|
1263
+ event = get_event_from_payload(payload)
1264
+ expect(event["metaData"]["some_tab"]).to eq({
1265
+ "[RECURSION]" => "[FILTERED]"
1266
+ })
1267
+ }
1268
+ end
1269
+
1270
+ it "uses a single '[RECURSION]'key when multiple keys recurse" do
1271
+ skip "JRuby doesn't allow recovery from SystemStackErrors" if is_jruby
1272
+
1273
+ Bugsnag.notify(BugsnagTestException.new("It crashed")) do |report|
1274
+ report.add_tab(:some_tab, {
1275
+ StringRecurser.new => 1,
1276
+ StringRecurser.new => 2
1277
+ })
1278
+ end
1279
+
1280
+ expect(Bugsnag).to have_sent_notification{ |payload, headers|
1281
+ event = get_event_from_payload(payload)
1282
+ expect(event["metaData"]["some_tab"]).to eq({
1283
+ "[RECURSION]" => "[FILTERED]"
1284
+ })
1285
+ }
1286
+ end
1287
+ end
1288
+
992
1289
  it 'should handle exceptions with empty backtrace' do
993
1290
  begin
994
1291
  err = RuntimeError.new
@@ -1280,3 +1577,4 @@ describe Bugsnag::Report do
1280
1577
  }
1281
1578
  end
1282
1579
  end
1580
+ # rubocop:enable Metrics/BlockLength