airbrake-ruby 4.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. checksums.yaml +7 -0
  2. data/lib/airbrake-ruby.rb +515 -0
  3. data/lib/airbrake-ruby/async_sender.rb +80 -0
  4. data/lib/airbrake-ruby/backtrace.rb +196 -0
  5. data/lib/airbrake-ruby/benchmark.rb +39 -0
  6. data/lib/airbrake-ruby/code_hunk.rb +51 -0
  7. data/lib/airbrake-ruby/config.rb +229 -0
  8. data/lib/airbrake-ruby/config/validator.rb +91 -0
  9. data/lib/airbrake-ruby/deploy_notifier.rb +36 -0
  10. data/lib/airbrake-ruby/file_cache.rb +54 -0
  11. data/lib/airbrake-ruby/filter_chain.rb +95 -0
  12. data/lib/airbrake-ruby/filters/context_filter.rb +29 -0
  13. data/lib/airbrake-ruby/filters/dependency_filter.rb +31 -0
  14. data/lib/airbrake-ruby/filters/exception_attributes_filter.rb +46 -0
  15. data/lib/airbrake-ruby/filters/gem_root_filter.rb +33 -0
  16. data/lib/airbrake-ruby/filters/git_last_checkout_filter.rb +92 -0
  17. data/lib/airbrake-ruby/filters/git_repository_filter.rb +64 -0
  18. data/lib/airbrake-ruby/filters/git_revision_filter.rb +66 -0
  19. data/lib/airbrake-ruby/filters/keys_blacklist.rb +49 -0
  20. data/lib/airbrake-ruby/filters/keys_filter.rb +140 -0
  21. data/lib/airbrake-ruby/filters/keys_whitelist.rb +48 -0
  22. data/lib/airbrake-ruby/filters/root_directory_filter.rb +28 -0
  23. data/lib/airbrake-ruby/filters/sql_filter.rb +125 -0
  24. data/lib/airbrake-ruby/filters/system_exit_filter.rb +23 -0
  25. data/lib/airbrake-ruby/filters/thread_filter.rb +92 -0
  26. data/lib/airbrake-ruby/hash_keyable.rb +37 -0
  27. data/lib/airbrake-ruby/ignorable.rb +44 -0
  28. data/lib/airbrake-ruby/inspectable.rb +39 -0
  29. data/lib/airbrake-ruby/loggable.rb +34 -0
  30. data/lib/airbrake-ruby/monotonic_time.rb +43 -0
  31. data/lib/airbrake-ruby/nested_exception.rb +38 -0
  32. data/lib/airbrake-ruby/notice.rb +162 -0
  33. data/lib/airbrake-ruby/notice_notifier.rb +134 -0
  34. data/lib/airbrake-ruby/performance_breakdown.rb +46 -0
  35. data/lib/airbrake-ruby/performance_notifier.rb +155 -0
  36. data/lib/airbrake-ruby/promise.rb +109 -0
  37. data/lib/airbrake-ruby/query.rb +54 -0
  38. data/lib/airbrake-ruby/request.rb +46 -0
  39. data/lib/airbrake-ruby/response.rb +74 -0
  40. data/lib/airbrake-ruby/stashable.rb +15 -0
  41. data/lib/airbrake-ruby/stat.rb +73 -0
  42. data/lib/airbrake-ruby/sync_sender.rb +113 -0
  43. data/lib/airbrake-ruby/tdigest.rb +393 -0
  44. data/lib/airbrake-ruby/thread_pool.rb +128 -0
  45. data/lib/airbrake-ruby/time_truncate.rb +17 -0
  46. data/lib/airbrake-ruby/timed_trace.rb +58 -0
  47. data/lib/airbrake-ruby/truncator.rb +115 -0
  48. data/lib/airbrake-ruby/version.rb +6 -0
  49. data/spec/airbrake_spec.rb +324 -0
  50. data/spec/async_sender_spec.rb +72 -0
  51. data/spec/backtrace_spec.rb +427 -0
  52. data/spec/benchmark_spec.rb +33 -0
  53. data/spec/code_hunk_spec.rb +115 -0
  54. data/spec/config/validator_spec.rb +184 -0
  55. data/spec/config_spec.rb +154 -0
  56. data/spec/deploy_notifier_spec.rb +48 -0
  57. data/spec/file_cache_spec.rb +34 -0
  58. data/spec/filter_chain_spec.rb +92 -0
  59. data/spec/filters/context_filter_spec.rb +23 -0
  60. data/spec/filters/dependency_filter_spec.rb +12 -0
  61. data/spec/filters/exception_attributes_filter_spec.rb +50 -0
  62. data/spec/filters/gem_root_filter_spec.rb +41 -0
  63. data/spec/filters/git_last_checkout_filter_spec.rb +46 -0
  64. data/spec/filters/git_repository_filter.rb +61 -0
  65. data/spec/filters/git_revision_filter_spec.rb +126 -0
  66. data/spec/filters/keys_blacklist_spec.rb +225 -0
  67. data/spec/filters/keys_whitelist_spec.rb +194 -0
  68. data/spec/filters/root_directory_filter_spec.rb +39 -0
  69. data/spec/filters/sql_filter_spec.rb +262 -0
  70. data/spec/filters/system_exit_filter_spec.rb +14 -0
  71. data/spec/filters/thread_filter_spec.rb +277 -0
  72. data/spec/fixtures/notroot.txt +7 -0
  73. data/spec/fixtures/project_root/code.rb +221 -0
  74. data/spec/fixtures/project_root/empty_file.rb +0 -0
  75. data/spec/fixtures/project_root/long_line.txt +1 -0
  76. data/spec/fixtures/project_root/short_file.rb +3 -0
  77. data/spec/fixtures/project_root/vendor/bundle/ignored_file.rb +5 -0
  78. data/spec/helpers.rb +9 -0
  79. data/spec/ignorable_spec.rb +14 -0
  80. data/spec/inspectable_spec.rb +45 -0
  81. data/spec/monotonic_time_spec.rb +12 -0
  82. data/spec/nested_exception_spec.rb +73 -0
  83. data/spec/notice_notifier/options_spec.rb +259 -0
  84. data/spec/notice_notifier_spec.rb +356 -0
  85. data/spec/notice_spec.rb +296 -0
  86. data/spec/performance_breakdown_spec.rb +12 -0
  87. data/spec/performance_notifier_spec.rb +491 -0
  88. data/spec/promise_spec.rb +197 -0
  89. data/spec/query_spec.rb +11 -0
  90. data/spec/request_spec.rb +11 -0
  91. data/spec/response_spec.rb +88 -0
  92. data/spec/spec_helper.rb +100 -0
  93. data/spec/stashable_spec.rb +23 -0
  94. data/spec/stat_spec.rb +47 -0
  95. data/spec/sync_sender_spec.rb +133 -0
  96. data/spec/tdigest_spec.rb +230 -0
  97. data/spec/thread_pool_spec.rb +158 -0
  98. data/spec/time_truncate_spec.rb +13 -0
  99. data/spec/timed_trace_spec.rb +125 -0
  100. data/spec/truncator_spec.rb +238 -0
  101. metadata +216 -0
@@ -0,0 +1,262 @@
1
+ RSpec.describe Airbrake::Filters::SqlFilter do
2
+ shared_examples "query filtering" do |test|
3
+ test[:dialects].each do |dialect|
4
+ it "correctly filters SQL like `#{test[:input]}' (#{dialect} dialect)" do
5
+ filter = described_class.new(dialect)
6
+ q = OpenStruct.new(query: test[:input])
7
+ filter.call(q)
8
+ expect(q.query).to eq(test[:output])
9
+ end
10
+ end
11
+ end
12
+
13
+ shared_examples "query blacklisting" do |query, opts|
14
+ it "ignores '#{query}'" do
15
+ filter = described_class.new('postgres')
16
+ q = Airbrake::Query.new(
17
+ query: query, method: 'GET', route: '/', start_time: Time.now
18
+ )
19
+ filter.call(q)
20
+
21
+ expect(q.ignored?).to eq(opts[:should_ignore])
22
+ end
23
+ end
24
+
25
+ ALL_DIALECTS = %i[mysql postgres sqlite cassandra oracle].freeze
26
+
27
+ # rubocop:disable Metrics/LineLength
28
+ [
29
+ {
30
+ input: 'SELECT * FROM things;',
31
+ output: 'SELECT * FROM things;',
32
+ dialects: ALL_DIALECTS
33
+ }, {
34
+ input: "SELECT `t001`.`c2` FROM `t001` WHERE `t001`.`c2` = 'value' AND c3=\"othervalue\" LIMIT ?",
35
+ output: "SELECT `t001`.`c2` FROM `t001` WHERE `t001`.`c2` = ? AND c3=? LIMIT ?",
36
+ dialects: %i[mysql]
37
+ }, {
38
+ input: "SELECT * FROM t WHERE foo=\"bar/*\" AND baz=\"whatever */qux\"",
39
+ output: "SELECT * FROM t WHERE foo=? AND baz=?",
40
+ dialects: %i[mysql]
41
+ }, {
42
+ input: "SELECT * FROM t WHERE foo='bar/*' AND baz='whatever */qux'",
43
+ output: "SELECT * FROM t WHERE foo=? AND baz=?",
44
+ dialects: ALL_DIALECTS
45
+ }, {
46
+ input: "SELECT \"t001\".\"c2\" FROM \"t001\" WHERE \"t001\".\"c2\" = 'value' AND c3=1234 LIMIT 1",
47
+ output: "SELECT \"t001\".\"c2\" FROM \"t001\" WHERE \"t001\".\"c2\" = ? AND c3=? LIMIT ?",
48
+ dialects: %i[postgres oracle]
49
+ }, {
50
+ input: "SELECT * FROM t WHERE foo=\"bar--\" AND\n baz=\"qux--\"",
51
+ output: "SELECT * FROM t WHERE foo=? AND\n baz=?",
52
+ dialects: %i[mysql]
53
+ }, {
54
+ input: "SELECT * FROM t WHERE foo='bar--' AND\n baz='qux--'",
55
+ output: "SELECT * FROM t WHERE foo=? AND\n baz=?",
56
+ dialects: ALL_DIALECTS
57
+ }, {
58
+ input: "SELECT * FROM foo WHERE bar='baz' /* Hide Me */",
59
+ output: "SELECT * FROM foo WHERE bar=? ?",
60
+ dialects: ALL_DIALECTS
61
+ }, {
62
+ input: "SELECT * FROM foobar WHERE password='hunter2'\n-- No peeking!",
63
+ output: "SELECT * FROM foobar WHERE password=?\n?",
64
+ dialects: ALL_DIALECTS
65
+ }, {
66
+ input: "SELECT foo, bar FROM baz WHERE password='hunter2' # Secret",
67
+ output: "SELECT foo, bar FROM baz WHERE password=? ?",
68
+ dialects: ALL_DIALECTS
69
+ }, {
70
+ input: "SELECT \"col1\", \"col2\" from \"table\" WHERE \"col3\"=E'foo\\'bar\\\\baz' AND country=e'foo\\'bar\\\\baz'",
71
+ output: "SELECT \"col1\", \"col2\" from \"table\" WHERE \"col3\"=E?",
72
+ dialects: %i[postgres]
73
+ }, {
74
+ input: "INSERT INTO `X` values(\"test\",0, 1 , 2, 'test')",
75
+ output: "INSERT INTO `X` values(?)",
76
+ dialects: %i[mysql]
77
+ }, {
78
+ input: "INSERT INTO `X` values(\"test\",0, 1 , 2, 'test')",
79
+ output: "INSERT INTO `X` values(?)",
80
+ dialects: %i[mysql]
81
+ }, {
82
+ input: "SELECT c11.col1, c22.col2 FROM table c11, table c22 WHERE value='nothing'",
83
+ output: "SELECT c11.col1, c22.col2 FROM table c11, table c22 WHERE value=?",
84
+ dialects: ALL_DIALECTS
85
+ }, {
86
+ input: "INSERT INTO X VALUES(1, 23456, 123.456, 99+100)",
87
+ output: "INSERT INTO X VALUES(?)",
88
+ dialects: ALL_DIALECTS
89
+ }, {
90
+ input: "SELECT * FROM table WHERE name=\"foo\" AND value=\"don't\"",
91
+ output: "SELECT * FROM table WHERE name=? AND value=?",
92
+ dialects: %i[mysql]
93
+ }, {
94
+ input: "SELECT * FROM table WHERE name='foo' AND value = 'bar'",
95
+ output: "SELECT * FROM table WHERE name=? AND value = ?",
96
+ dialects: ALL_DIALECTS
97
+ }, {
98
+ input: "SELECT * FROM table WHERE col='foo\\''bar'",
99
+ output: "SELECT * FROM table WHERE col=?",
100
+ dialects: ALL_DIALECTS
101
+ }, {
102
+ input: "SELECT * FROM table WHERE col1='foo\"bar' AND col2='what\"ever'",
103
+ output: "SELECT * FROM table WHERE col1=? AND col2=?",
104
+ dialects: ALL_DIALECTS
105
+ }, {
106
+ input: "select * from accounts where accounts.name != 'dude\n newline' order by accounts.name",
107
+ output: "select * from accounts where accounts.name != ? order by accounts.name",
108
+ dialects: ALL_DIALECTS
109
+ }, {
110
+ input: "SELECT * FROM table WHERE col1=\"don't\" AND col2=\"won't\"",
111
+ output: "SELECT * FROM table WHERE col1=? AND col2=?",
112
+ dialects: %i[mysql]
113
+ }, {
114
+ input: "INSERT INTO X values('', 'jim''s ssn',0, 1 , 'jim''s son''s son', \"\"\"jim''s\"\" hat\", \"\\\"jim''s secret\\\"\")",
115
+ output: "INSERT INTO X values(?, ?,?, ? , ?, ?, ?",
116
+ dialects: %i[mysql]
117
+ }, {
118
+ input: "SELECT * FROM table WHERE name='foo\\' AND color='blue'",
119
+ output: "SELECT * FROM table WHERE name=?",
120
+ dialects: ALL_DIALECTS
121
+ }, {
122
+ input: "SELECT * FROM table WHERE foo=\"this string ends with a backslash\\\\\"",
123
+ output: "SELECT * FROM table WHERE foo=?",
124
+ dialects: %i[mysql]
125
+ }, {
126
+ input: "SELECT * FROM table WHERE foo='this string ends with a backslash\\\\'",
127
+ output: "SELECT * FROM table WHERE foo=?",
128
+ dialects: ALL_DIALECTS
129
+ }, {
130
+ # TODO: fix this example.
131
+ input: "SELECT * FROM table WHERE name='foo\'' AND color='blue'",
132
+ output: "Error: Airbrake::Query was not filtered",
133
+ dialects: ALL_DIALECTS
134
+ }, {
135
+ input: "INSERT INTO X values('', 'a''b c',0, 1 , 'd''e f''s h')",
136
+ output: "INSERT INTO X values(?)",
137
+ dialects: ALL_DIALECTS
138
+ }, {
139
+ input: "SELECT * FROM t WHERE -- '\n bar='baz' -- '",
140
+ output: "SELECT * FROM t WHERE ?\n bar=? ?",
141
+ dialects: ALL_DIALECTS
142
+ }, {
143
+ input: "SELECT * FROM t WHERE /* ' */\n bar='baz' -- '",
144
+ output: "SELECT * FROM t WHERE ?\n bar=? ?",
145
+ dialects: ALL_DIALECTS
146
+ }, {
147
+ input: "SELECT * FROM t WHERE -- '\n /* ' */ c2='xxx' /* ' */\n c='x\n xx' -- '",
148
+ output: "SELECT * FROM t WHERE ?\n ? c2=? ?\n c=? ?",
149
+ dialects: ALL_DIALECTS
150
+ }, {
151
+ input: "SELECT * FROM t WHERE -- '\n c='x\n xx' -- '",
152
+ output: "SELECT * FROM t WHERE ?\n c=? ?",
153
+ dialects: ALL_DIALECTS
154
+ }, {
155
+ input: "SELECT * FROM foo WHERE col='value1' AND /* don't */ col2='value1' /* won't */",
156
+ output: "SELECT * FROM foo WHERE col=? AND ? col2=? ?",
157
+ dialects: ALL_DIALECTS
158
+ }, {
159
+ input: "SELECT * FROM table WHERE foo='bar' AND baz=\"nothing to see here'",
160
+ output: "Error: Airbrake::Query was not filtered",
161
+ dialects: %i[mysql]
162
+ }, {
163
+ input: "SELECT * FROM table WHERE foo='bar' AND baz='nothing to see here",
164
+ output: "Error: Airbrake::Query was not filtered",
165
+ dialects: ALL_DIALECTS
166
+ }, {
167
+ input: "SELECT * FROM \"foo\" WHERE \"foo\" = $a$dollar quotes can be $b$nested$b$$a$ and bar = 'baz'",
168
+ output: "SELECT * FROM \"foo\" WHERE \"foo\" = ? and bar = ?",
169
+ dialects: %i[postgres]
170
+ }, {
171
+ input: "INSERT INTO \"foo\" (\"bar\", \"baz\", \"qux\") VALUES ($1, $2, $3) RETURNING \"id\"",
172
+ output: "INSERT INTO \"foo\" (?) RETURNING \"id\"",
173
+ dialects: %i[postgres]
174
+ }, {
175
+ input: "select * from foo where bar = 'some\\tthing' and baz = 10",
176
+ output: "select * from foo where bar = ? and baz = ?",
177
+ dialects: ALL_DIALECTS
178
+ }, {
179
+ input: "select * from users where user = 'user1\\' password = 'hunter 2' -- ->don't count this quote",
180
+ output: "select * from users where user = ?",
181
+ dialects: ALL_DIALECTS
182
+ }, {
183
+ input: "select * from foo where bar=q'[baz's]' and x=5",
184
+ output: "select * from foo where bar=? and x=?",
185
+ dialects: %i[oracle]
186
+ }, {
187
+ input: "select * from foo where bar=q'{baz's}' and x=5",
188
+ output: "select * from foo where bar=? and x=?",
189
+ dialects: %i[oracle]
190
+ }, {
191
+ input: "select * from foo where bar=q'<baz's>' and x=5",
192
+ output: "select * from foo where bar=? and x=?",
193
+ dialects: %i[oracle]
194
+ }, {
195
+ input: "select * from foo where bar=q'(baz's)' and x=5",
196
+ output: "select * from foo where bar=? and x=?",
197
+ dialects: %i[oracle]
198
+ }, {
199
+ input: "select * from foo where bar=0xabcdef123 and x=5",
200
+ output: "select * from foo where bar=? and x=?",
201
+ dialects: %i[cassandra sqlite]
202
+ }, {
203
+ input: "select * from foo where bar=0x2F and x=5",
204
+ output: "select * from foo where bar=? and x=?",
205
+ dialects: %i[mysql cassandra sqlite]
206
+ }, {
207
+ input: "select * from foo where bar=1.234e-5 and x=5",
208
+ output: "select * from foo where bar=? and x=?",
209
+ dialects: ALL_DIALECTS
210
+ }, {
211
+ input: "select * from foo where bar=01234567-89ab-cdef-0123-456789abcdef and x=5",
212
+ output: "select * from foo where bar=? and x=?",
213
+ dialects: %i[postgres cassandra]
214
+ }, {
215
+ input: "select * from foo where bar={01234567-89ab-cdef-0123-456789abcdef} and x=5",
216
+ output: "select * from foo where bar=? and x=?",
217
+ dialects: %i[postgres]
218
+ }, {
219
+ input: "select * from foo where bar=0123456789abcdef0123456789abcdef and x=5",
220
+ output: "select * from foo where bar=? and x=?",
221
+ dialects: %i[postgtes]
222
+ }, {
223
+ input: "select * from foo where bar={012-345678-9abc-def012345678-9abcdef} and x=5",
224
+ output: "select * from foo where bar=? and x=?",
225
+ dialects: %i[postgres]
226
+ }, {
227
+ input: "select * from foo where bar=true and x=FALSE",
228
+ output: "select * from foo where bar=? and x=?",
229
+ dialects: %i[mysql postgres cassandra sqlite]
230
+ }
231
+ ].each do |test|
232
+ include_examples 'query filtering', test
233
+ end
234
+ # rubocop:enable Metrics/LineLength
235
+
236
+ [
237
+ 'COMMIT',
238
+ 'commit',
239
+ 'BEGIN',
240
+ 'begin',
241
+ 'SET time zone ?',
242
+ 'set time zone ?',
243
+ 'SHOW max_identifier_length',
244
+ 'show max_identifier_length',
245
+
246
+ 'WITH pk_constraint AS ( SELECT conrelid, unnest(conkey) AS connum ' \
247
+ 'FROM pg_constraint WHERE contype = ? AND conrelid = ?::regclass ), ' \
248
+ 'cons AS ( SELECT conrelid, connum, row_number() OVER() AS rownum FROM ' \
249
+ 'pk_constraint ) SELECT attr.attname FROM pg_attribute attr INNER JOIN ' \
250
+ 'cons ON attr.attrelid = cons.conrelid AND attr.attnum = cons.connum ' \
251
+ 'ORDER BY cons.rownum'
252
+
253
+ ].each do |query|
254
+ include_examples 'query blacklisting', query, should_ignore: true
255
+ end
256
+
257
+ [
258
+ 'UPDATE "users" SET "last_sign_in_at" = ? WHERE "users"."id" = ?'
259
+ ].each do |query|
260
+ include_examples 'query blacklisting', query, should_ignore: false
261
+ end
262
+ end
@@ -0,0 +1,14 @@
1
+ RSpec.describe Airbrake::Filters::SystemExitFilter do
2
+ it "marks SystemExit exceptions as ignored" do
3
+ notice = Airbrake::Notice.new(SystemExit.new)
4
+ expect { subject.call(notice) }.to(
5
+ change { notice.ignored? }.from(false).to(true)
6
+ )
7
+ end
8
+
9
+ it "doesn't mark non SystemExit exceptions as ignored" do
10
+ notice = Airbrake::Notice.new(AirbrakeTestError.new)
11
+ expect(notice).not_to be_ignored
12
+ expect { subject.call(notice) }.not_to(change { notice.ignored? })
13
+ end
14
+ end
@@ -0,0 +1,277 @@
1
+ RSpec.describe Airbrake::Filters::ThreadFilter do
2
+ let(:notice) { Airbrake::Notice.new(AirbrakeTestError.new) }
3
+
4
+ def new_thread
5
+ Thread.new do
6
+ th = Thread.current
7
+
8
+ # Ensure a thread always has some variable to make sure the
9
+ # :fiber_variables Hash is always present.
10
+ th[:random_var] = 42
11
+ yield(th)
12
+ end.join
13
+ end
14
+
15
+ describe "thread variables" do
16
+ shared_examples "expected thread variable" do |var|
17
+ it "attaches the thread variable" do
18
+ new_thread do |th|
19
+ th.thread_variable_set(:bingo, var)
20
+ subject.call(notice)
21
+ end
22
+
23
+ expect(notice[:params][:thread][:thread_variables][:bingo]).to eq(var)
24
+ end
25
+ end
26
+
27
+ context "given nil" do
28
+ include_examples "expected thread variable", nil
29
+ end
30
+
31
+ context "given true" do
32
+ include_examples "expected thread variable", true
33
+ end
34
+
35
+ context "given false" do
36
+ include_examples "expected thread variable", false
37
+ end
38
+
39
+ context "given a String" do
40
+ include_examples "expected thread variable", 'bango'
41
+ end
42
+
43
+ context "given a Symbol" do
44
+ include_examples "expected thread variable", :bango
45
+ end
46
+
47
+ context "given a Regexp" do
48
+ include_examples "expected thread variable", /bango/
49
+ end
50
+
51
+ context "given an Integer" do
52
+ include_examples "expected thread variable", 1
53
+ end
54
+
55
+ context "given a Float" do
56
+ include_examples "expected thread variable", 1.01
57
+ end
58
+
59
+ context "given an Object" do
60
+ it "converts it to a String and attaches" do
61
+ new_thread do |th|
62
+ th.thread_variable_set(:bingo, Object.new)
63
+ subject.call(notice)
64
+ end
65
+
66
+ vars = notice[:params][:thread][:thread_variables]
67
+ expect(vars[:bingo]).to match(/\A#<Object:.+>\z/)
68
+ end
69
+ end
70
+
71
+ context "given an Array of nested Hashes with complex objects" do
72
+ let(:var) do
73
+ [
74
+ {
75
+ bango: {
76
+ bongo: [
77
+ {
78
+ bish: {
79
+ bash: 'foo',
80
+ bosh: Object.new
81
+ }
82
+ }
83
+ ]
84
+ }
85
+ },
86
+ 123
87
+ ]
88
+ end
89
+
90
+ it "converts objects to a safe objects" do
91
+ new_thread do |th|
92
+ th.thread_variable_set(:bingo, var)
93
+ subject.call(notice)
94
+ end
95
+
96
+ vars = notice[:params][:thread][:thread_variables]
97
+ expect(vars[:bingo]).to(
98
+ match(
99
+ [
100
+ {
101
+ bango: {
102
+ bongo: [
103
+ {
104
+ bish: {
105
+ bash: 'foo',
106
+ bosh: /\A#<Object:.+>\z/
107
+ }
108
+ }
109
+ ]
110
+ }
111
+ },
112
+ 123
113
+ ]
114
+ )
115
+ )
116
+ end
117
+ end
118
+
119
+ it "ignores thread variables starting with an underscore" do
120
+ var = :__recursive_key__
121
+
122
+ new_thread do |th|
123
+ th.thread_variable_set(var, :bingo)
124
+ subject.call(notice)
125
+ end
126
+
127
+ thread_variables = notice[:params][:thread][:thread_variables]
128
+ expect(thread_variables).to be_nil
129
+ end
130
+ end
131
+
132
+ describe "fiber variables" do
133
+ shared_examples "expected fiber variable" do |var|
134
+ it "attaches the fiber variable" do
135
+ new_thread do |th|
136
+ th[:bingo] = var
137
+ subject.call(notice)
138
+ end
139
+
140
+ expect(notice[:params][:thread][:fiber_variables][:bingo]).to eq(var)
141
+ end
142
+ end
143
+
144
+ context "given nil" do
145
+ include_examples "expected fiber variable", nil
146
+ end
147
+
148
+ context "given true" do
149
+ include_examples "expected fiber variable", true
150
+ end
151
+
152
+ context "given false" do
153
+ include_examples "expected fiber variable", false
154
+ end
155
+
156
+ context "given a String" do
157
+ include_examples "expected fiber variable", 'bango'
158
+ end
159
+
160
+ context "given a Symbol" do
161
+ include_examples "expected fiber variable", :bango
162
+ end
163
+
164
+ context "given a Regexp" do
165
+ include_examples "expected fiber variable", /bango/
166
+ end
167
+
168
+ context "given an Integer" do
169
+ include_examples "expected fiber variable", 1
170
+ end
171
+
172
+ context "given a Float" do
173
+ include_examples "expected fiber variable", 1.01
174
+ end
175
+
176
+ context "given an Object" do
177
+ it "converts it to a String and attaches" do
178
+ new_thread do |th|
179
+ th[:bingo] = Object.new
180
+ subject.call(notice)
181
+ end
182
+
183
+ vars = notice[:params][:thread][:fiber_variables]
184
+ expect(vars[:bingo]).to match(/\A#<Object:.+>\z/)
185
+ end
186
+ end
187
+
188
+ context "given an Array of nested Hashes with complex objects" do
189
+ let(:var) do
190
+ [
191
+ {
192
+ bango: {
193
+ bongo: [
194
+ {
195
+ bish: {
196
+ bash: 'foo',
197
+ bosh: Object.new
198
+ }
199
+ }
200
+ ]
201
+ }
202
+ },
203
+ 123
204
+ ]
205
+ end
206
+
207
+ it "converts objects to a safe objects" do
208
+ new_thread do |th|
209
+ th[:bingo] = var
210
+ subject.call(notice)
211
+ end
212
+
213
+ vars = notice[:params][:thread][:fiber_variables]
214
+ expect(vars[:bingo]).to(
215
+ match(
216
+ [
217
+ {
218
+ bango: {
219
+ bongo: [
220
+ {
221
+ bish: {
222
+ bash: 'foo',
223
+ bosh: /\A#<Object:.+>\z/
224
+ }
225
+ }
226
+ ]
227
+ }
228
+ },
229
+ 123
230
+ ]
231
+ )
232
+ )
233
+ end
234
+ end
235
+ end
236
+
237
+ it "appends name", skip: !Thread.current.respond_to?(:name) do
238
+ new_thread do |th|
239
+ th.name = 'bingo'
240
+ subject.call(notice)
241
+ end
242
+
243
+ expect(notice[:params][:thread][:name]).to eq('bingo')
244
+ end
245
+
246
+ it "appends thread inspect (self)" do
247
+ subject.call(notice)
248
+ expect(notice[:params][:thread][:self]).to match(/\A#<Thread:.+>\z/)
249
+ end
250
+
251
+ it "appends thread group" do
252
+ subject.call(notice)
253
+ expect(notice[:params][:thread][:group][0]).to match(/\A#<Thread:.+>\z/)
254
+ end
255
+
256
+ it "appends priority" do
257
+ subject.call(notice)
258
+ expect(notice[:params][:thread][:priority]).to eq(0)
259
+ end
260
+
261
+ it "appends safe_level", skip: Airbrake::JRUBY do
262
+ subject.call(notice)
263
+ expect(notice[:params][:thread][:safe_level]).to eq(0)
264
+ end
265
+
266
+ it "ignores fiber variables starting with an underscore" do
267
+ key = :__recursive_key__
268
+
269
+ new_thread do |th|
270
+ th[key] = :bingo
271
+ subject.call(notice)
272
+ end
273
+
274
+ fiber_variables = notice[:params][:thread][:fiber_variables]
275
+ expect(fiber_variables[key]).to be_nil
276
+ end
277
+ end