bugsnag 6.12.1 → 6.15.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (141) 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 +73 -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 +32 -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/report_modification/initiators/handled_on_error.rb +10 -0
  31. data/features/fixtures/plain/app/report_modification/initiators/unhandled_on_error.rb +11 -0
  32. data/features/fixtures/plain/app/stack_frame_modification/initiators/handled_on_error.rb +29 -0
  33. data/features/fixtures/plain/app/stack_frame_modification/initiators/unhandled_on_error.rb +26 -0
  34. data/features/fixtures/plain/app/unhandled/{Interrupt.rb → interrupt.rb} +0 -0
  35. data/features/fixtures/rack1/Dockerfile +2 -2
  36. data/features/fixtures/rack2/Dockerfile +2 -2
  37. data/features/fixtures/rails3/Dockerfile +2 -2
  38. data/features/fixtures/rails3/app/Gemfile +4 -0
  39. data/features/fixtures/rails3/app/config/initializers/bugsnag.rb +11 -2
  40. data/features/fixtures/rails4/Dockerfile +2 -5
  41. data/features/fixtures/rails4/app/config/initializers/bugsnag.rb +10 -1
  42. data/features/fixtures/rails5/Dockerfile +2 -2
  43. data/features/fixtures/rails5/app/Gemfile +3 -2
  44. data/features/fixtures/rails5/app/config/initializers/bugsnag.rb +10 -1
  45. data/features/fixtures/rails6/Dockerfile +2 -2
  46. data/features/fixtures/rails6/app/Gemfile +3 -2
  47. data/features/fixtures/rails6/app/app/controllers/mongo_controller.rb +22 -0
  48. data/features/fixtures/rails6/app/app/models/mongo_model.rb +6 -0
  49. data/features/fixtures/rails6/app/config/environments/development.rb +2 -0
  50. data/features/fixtures/rails6/app/config/environments/production.rb +1 -0
  51. data/features/fixtures/rails6/app/config/environments/rails_env.rb +1 -0
  52. data/features/fixtures/rails6/app/config/environments/test.rb +1 -0
  53. data/features/fixtures/rails6/app/config/initializers/bugsnag.rb +10 -1
  54. data/features/fixtures/rails6/app/config/mongoid.yml +23 -0
  55. data/features/fixtures/rails6/app/config/routes.rb +4 -0
  56. data/features/fixtures/resque/Dockerfile +2 -2
  57. data/features/fixtures/sidekiq/Dockerfile +5 -7
  58. data/features/fixtures/sidekiq/app/Gemfile +2 -1
  59. data/features/fixtures/sidekiq/app/Rakefile.rb +14 -0
  60. data/features/fixtures/sinatra1/Dockerfile +2 -2
  61. data/features/fixtures/sinatra2/Dockerfile +2 -2
  62. data/features/plain_features/add_tab.feature +30 -97
  63. data/features/plain_features/app_type.feature +6 -25
  64. data/features/plain_features/app_version.feature +6 -25
  65. data/features/plain_features/auto_notify.feature +4 -20
  66. data/features/plain_features/delivery.feature +12 -60
  67. data/features/plain_features/exception_data.feature +24 -94
  68. data/features/plain_features/filters.feature +9 -43
  69. data/features/plain_features/handled_errors.feature +16 -78
  70. data/features/plain_features/ignore_classes.feature +5 -23
  71. data/features/plain_features/ignore_report.feature +8 -24
  72. data/features/plain_features/proxies.feature +13 -56
  73. data/features/plain_features/release_stages.feature +9 -40
  74. data/features/plain_features/report_api_key.feature +11 -35
  75. data/features/plain_features/report_severity.feature +10 -35
  76. data/features/plain_features/report_stack_frames.feature +29 -93
  77. data/features/plain_features/report_user.feature +29 -96
  78. data/features/plain_features/unhandled_errors.feature +17 -88
  79. data/features/rails_features/api_key.feature +12 -58
  80. data/features/rails_features/app_type.feature +13 -58
  81. data/features/rails_features/app_version.feature +19 -80
  82. data/features/rails_features/auto_capture_sessions.feature +31 -112
  83. data/features/rails_features/auto_notify.feature +28 -105
  84. data/features/rails_features/before_notify.feature +18 -83
  85. data/features/rails_features/breadcrumbs.feature +40 -137
  86. data/features/rails_features/handled.feature +18 -82
  87. data/features/rails_features/ignore_classes.feature +12 -51
  88. data/features/rails_features/meta_data_filters.feature +9 -33
  89. data/features/rails_features/mongo_breadcrumbs.feature +22 -96
  90. data/features/rails_features/on_error.feature +29 -0
  91. data/features/rails_features/project_root.feature +19 -84
  92. data/features/rails_features/release_stage.feature +20 -82
  93. data/features/rails_features/send_code.feature +13 -55
  94. data/features/rails_features/send_environment.feature +7 -33
  95. data/features/rails_features/unhandled.feature +6 -31
  96. data/features/rails_features/user_info.feature +27 -65
  97. data/features/sidekiq.feature +12 -79
  98. data/features/steps/ruby_notifier_steps.rb +59 -15
  99. data/features/support/env.rb +12 -45
  100. data/lib/bugsnag.rb +109 -21
  101. data/lib/bugsnag/breadcrumbs/breadcrumbs.rb +0 -2
  102. data/lib/bugsnag/breadcrumbs/validator.rb +0 -6
  103. data/lib/bugsnag/cleaner.rb +129 -60
  104. data/lib/bugsnag/code_extractor.rb +137 -0
  105. data/lib/bugsnag/configuration.rb +58 -1
  106. data/lib/bugsnag/helpers.rb +2 -4
  107. data/lib/bugsnag/integrations/que.rb +7 -4
  108. data/lib/bugsnag/middleware/discard_error_class.rb +30 -0
  109. data/lib/bugsnag/middleware/exception_meta_data.rb +15 -9
  110. data/lib/bugsnag/middleware/ignore_error_class.rb +2 -0
  111. data/lib/bugsnag/middleware/rack_request.rb +2 -4
  112. data/lib/bugsnag/middleware_stack.rb +38 -3
  113. data/lib/bugsnag/on_error_callbacks.rb +33 -0
  114. data/lib/bugsnag/report.rb +4 -14
  115. data/lib/bugsnag/session_tracker.rb +3 -3
  116. data/lib/bugsnag/stacktrace.rb +28 -75
  117. data/spec/breadcrumbs/breadcrumb_spec.rb +1 -1
  118. data/spec/breadcrumbs/validator_spec.rb +1 -26
  119. data/spec/bugsnag_spec.rb +2 -2
  120. data/spec/cleaner_spec.rb +202 -10
  121. data/spec/code_extractor_spec.rb +129 -0
  122. data/spec/configuration_spec.rb +16 -1
  123. data/spec/fixtures/apps/rails-initializer-config/Gemfile +5 -1
  124. data/spec/fixtures/apps/rails-invalid-initializer-config/Gemfile +5 -1
  125. data/spec/fixtures/apps/rails-no-config/Gemfile +5 -1
  126. data/spec/fixtures/crashes/file1.rb +29 -0
  127. data/spec/fixtures/crashes/file2.rb +25 -0
  128. data/spec/fixtures/crashes/file_with_long_lines.rb +7 -0
  129. data/spec/fixtures/crashes/functions.rb +29 -0
  130. data/spec/fixtures/crashes/short_file.rb +2 -0
  131. data/spec/helper_spec.rb +0 -31
  132. data/spec/integrations/logger_spec.rb +1 -1
  133. data/spec/integrations/rack_spec.rb +8 -6
  134. data/spec/integrations/rake_spec.rb +1 -1
  135. data/spec/on_error_spec.rb +332 -0
  136. data/spec/report_spec.rb +331 -30
  137. data/spec/spec_helper.rb +14 -1
  138. data/spec/stacktrace_spec.rb +427 -74
  139. metadata +36 -7
  140. data/.travis.yml +0 -117
  141. data/features/plain_features/api_key.feature +0 -25
@@ -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
@@ -1076,27 +1373,30 @@ describe Bugsnag::Report do
1076
1373
  notify_test_exception
1077
1374
  expect(Bugsnag).to have_sent_notification{ |payload, headers|
1078
1375
  exception = get_exception_from_payload(payload)
1376
+
1079
1377
  bugsnag_count = 0
1378
+
1080
1379
  exception["stacktrace"].each do |frame|
1081
1380
  if /.*lib\/bugsnag.*\.rb/.match(frame["file"])
1082
1381
  bugsnag_count += 1
1083
1382
  expect(frame["inProject"]).to be_nil
1084
1383
  end
1085
1384
  end
1086
- # 7 is used here as the called bugsnag frames for a `notify` call should be:
1385
+
1386
+ # 6 is used here as the called bugsnag frames for a `notify` call should be:
1087
1387
  # - Bugsnag.notify
1088
1388
  # - Report.new
1089
1389
  # - Report.initialize
1090
1390
  # - Report.generate_exceptions_list
1091
1391
  # - Report.generate_exceptions_list | raw_exceptions.map
1092
1392
  # - Report.generate_exceptions_list | raw_exceptions.map | block
1093
- # - Report.generate_exceptions_list | raw_exceptions.map | block | Stacktrace.new
1094
- # However, JRUBY does not include the two `new` frames, resulting in 5 bugsnag frames
1393
+ # However, JRUBY does not include the `Report.new` frame, resulting in 5 bugsnag frames
1095
1394
  if defined?(JRUBY_VERSION)
1096
1395
  frame_count = 5
1097
1396
  else
1098
- frame_count = 7
1397
+ frame_count = 6
1099
1398
  end
1399
+
1100
1400
  expect(bugsnag_count).to equal frame_count
1101
1401
  }
1102
1402
  end
@@ -1280,3 +1580,4 @@ describe Bugsnag::Report do
1280
1580
  }
1281
1581
  end
1282
1582
  end
1583
+ # rubocop:enable Metrics/BlockLength