appsignal 3.9.3-java → 3.11.0-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 (103) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +22 -19
  3. data/.rubocop.yml +1 -1
  4. data/CHANGELOG.md +180 -0
  5. data/Gemfile +1 -0
  6. data/README.md +0 -1
  7. data/Rakefile +1 -1
  8. data/benchmark.rake +99 -42
  9. data/build_matrix.yml +10 -12
  10. data/gemfiles/webmachine1.gemfile +5 -4
  11. data/lib/appsignal/cli/demo.rb +0 -1
  12. data/lib/appsignal/config.rb +57 -97
  13. data/lib/appsignal/demo.rb +15 -20
  14. data/lib/appsignal/environment.rb +6 -1
  15. data/lib/appsignal/event_formatter/rom/sql_formatter.rb +1 -0
  16. data/lib/appsignal/event_formatter.rb +3 -2
  17. data/lib/appsignal/helpers/instrumentation.rb +490 -16
  18. data/lib/appsignal/hooks/action_cable.rb +21 -16
  19. data/lib/appsignal/hooks/active_job.rb +15 -14
  20. data/lib/appsignal/hooks/delayed_job.rb +1 -1
  21. data/lib/appsignal/hooks/shoryuken.rb +3 -63
  22. data/lib/appsignal/integrations/action_cable.rb +5 -7
  23. data/lib/appsignal/integrations/active_support_notifications.rb +1 -0
  24. data/lib/appsignal/integrations/capistrano/capistrano_2_tasks.rb +36 -35
  25. data/lib/appsignal/integrations/data_mapper.rb +1 -0
  26. data/lib/appsignal/integrations/delayed_job_plugin.rb +27 -33
  27. data/lib/appsignal/integrations/dry_monitor.rb +1 -0
  28. data/lib/appsignal/integrations/excon.rb +1 -0
  29. data/lib/appsignal/integrations/http.rb +1 -0
  30. data/lib/appsignal/integrations/net_http.rb +1 -0
  31. data/lib/appsignal/integrations/object.rb +6 -0
  32. data/lib/appsignal/integrations/padrino.rb +21 -25
  33. data/lib/appsignal/integrations/que.rb +13 -20
  34. data/lib/appsignal/integrations/railtie.rb +1 -1
  35. data/lib/appsignal/integrations/rake.rb +45 -15
  36. data/lib/appsignal/integrations/redis.rb +1 -0
  37. data/lib/appsignal/integrations/redis_client.rb +1 -0
  38. data/lib/appsignal/integrations/resque.rb +2 -5
  39. data/lib/appsignal/integrations/shoryuken.rb +75 -0
  40. data/lib/appsignal/integrations/sidekiq.rb +7 -25
  41. data/lib/appsignal/integrations/unicorn.rb +1 -0
  42. data/lib/appsignal/integrations/webmachine.rb +12 -9
  43. data/lib/appsignal/logger.rb +7 -3
  44. data/lib/appsignal/probes/helpers.rb +1 -0
  45. data/lib/appsignal/probes/mri.rb +1 -0
  46. data/lib/appsignal/probes/sidekiq.rb +1 -0
  47. data/lib/appsignal/probes.rb +3 -0
  48. data/lib/appsignal/rack/abstract_middleware.rb +67 -24
  49. data/lib/appsignal/rack/body_wrapper.rb +143 -0
  50. data/lib/appsignal/rack/event_handler.rb +39 -8
  51. data/lib/appsignal/rack/generic_instrumentation.rb +6 -4
  52. data/lib/appsignal/rack/grape_middleware.rb +3 -2
  53. data/lib/appsignal/rack/hanami_middleware.rb +1 -1
  54. data/lib/appsignal/rack/instrumentation_middleware.rb +62 -0
  55. data/lib/appsignal/rack/rails_instrumentation.rb +1 -3
  56. data/lib/appsignal/rack/sinatra_instrumentation.rb +1 -3
  57. data/lib/appsignal/rack/streaming_listener.rb +14 -59
  58. data/lib/appsignal/rack.rb +60 -0
  59. data/lib/appsignal/span.rb +1 -0
  60. data/lib/appsignal/transaction.rb +353 -104
  61. data/lib/appsignal/utils/data.rb +0 -1
  62. data/lib/appsignal/utils/hash_sanitizer.rb +0 -1
  63. data/lib/appsignal/utils/integration_logger.rb +0 -13
  64. data/lib/appsignal/utils/integration_memory_logger.rb +0 -13
  65. data/lib/appsignal/utils/json.rb +0 -1
  66. data/lib/appsignal/utils/query_params_sanitizer.rb +0 -1
  67. data/lib/appsignal/utils/stdout_and_logger_message.rb +0 -1
  68. data/lib/appsignal/utils.rb +6 -0
  69. data/lib/appsignal/version.rb +1 -1
  70. data/lib/appsignal.rb +9 -6
  71. data/spec/lib/appsignal/capistrano2_spec.rb +1 -1
  72. data/spec/lib/appsignal/config_spec.rb +139 -43
  73. data/spec/lib/appsignal/hooks/action_cable_spec.rb +43 -74
  74. data/spec/lib/appsignal/hooks/activejob_spec.rb +9 -0
  75. data/spec/lib/appsignal/hooks/delayed_job_spec.rb +2 -443
  76. data/spec/lib/appsignal/hooks/rake_spec.rb +100 -17
  77. data/spec/lib/appsignal/hooks/shoryuken_spec.rb +0 -171
  78. data/spec/lib/appsignal/integrations/delayed_job_plugin_spec.rb +459 -0
  79. data/spec/lib/appsignal/integrations/padrino_spec.rb +181 -131
  80. data/spec/lib/appsignal/integrations/que_spec.rb +3 -4
  81. data/spec/lib/appsignal/integrations/shoryuken_spec.rb +167 -0
  82. data/spec/lib/appsignal/integrations/sidekiq_spec.rb +4 -4
  83. data/spec/lib/appsignal/integrations/sinatra_spec.rb +10 -2
  84. data/spec/lib/appsignal/integrations/webmachine_spec.rb +77 -17
  85. data/spec/lib/appsignal/rack/abstract_middleware_spec.rb +144 -11
  86. data/spec/lib/appsignal/rack/body_wrapper_spec.rb +263 -0
  87. data/spec/lib/appsignal/rack/event_handler_spec.rb +81 -10
  88. data/spec/lib/appsignal/rack/generic_instrumentation_spec.rb +70 -17
  89. data/spec/lib/appsignal/rack/grape_middleware_spec.rb +1 -1
  90. data/spec/lib/appsignal/rack/instrumentation_middleware_spec.rb +38 -0
  91. data/spec/lib/appsignal/rack/rails_instrumentation_spec.rb +4 -2
  92. data/spec/lib/appsignal/rack/streaming_listener_spec.rb +43 -120
  93. data/spec/lib/appsignal/rack_spec.rb +63 -0
  94. data/spec/lib/appsignal/transaction_spec.rb +1675 -953
  95. data/spec/lib/appsignal/utils/integration_logger_spec.rb +12 -16
  96. data/spec/lib/appsignal/utils/integration_memory_logger_spec.rb +0 -10
  97. data/spec/lib/appsignal_spec.rb +517 -13
  98. data/spec/support/helpers/transaction_helpers.rb +44 -20
  99. data/spec/support/matchers/transaction.rb +15 -1
  100. data/spec/support/mocks/dummy_app.rb +1 -1
  101. data/spec/support/testing.rb +1 -1
  102. metadata +12 -4
  103. data/support/check_versions +0 -22
@@ -15,9 +15,7 @@ describe Appsignal::Hooks::DelayedJobHook do
15
15
  end
16
16
  end
17
17
  after(:context) { Object.send(:remove_const, :Delayed) }
18
- before do
19
- start_agent
20
- end
18
+ before { start_agent }
21
19
 
22
20
  describe "#dependencies_present?" do
23
21
  subject { described_class.new.dependencies_present? }
@@ -26,446 +24,7 @@ describe Appsignal::Hooks::DelayedJobHook do
26
24
  end
27
25
 
28
26
  it "adds the plugin" do
29
- expect(::Delayed::Worker.plugins).to include Appsignal::Hooks::DelayedJobPlugin
30
- end
31
-
32
- # We haven't found a way to test the hooks, we'll have to do that manually
33
-
34
- describe ".invoke_with_instrumentation" do
35
- let(:plugin) { Appsignal::Hooks::DelayedJobPlugin }
36
- let(:time) { Time.parse("01-01-2001 10:01:00UTC") }
37
- let(:created_at) { time - 3600 }
38
- let(:run_at) { time - 3600 }
39
- let(:payload_object) { double(:args => args) }
40
- let(:job_data) do
41
- {
42
- :id => 123,
43
- :name => "TestClass#perform",
44
- :priority => 1,
45
- :attempts => 1,
46
- :queue => "default",
47
- :created_at => created_at,
48
- :run_at => run_at,
49
- :payload_object => payload_object
50
- }
51
- end
52
- let(:args) { ["argument"] }
53
- let(:job) { double(job_data) }
54
- let(:invoked_block) { proc {} }
55
-
56
- def perform
57
- Timecop.freeze(time) do
58
- keep_transactions do
59
- plugin.invoke_with_instrumentation(job, invoked_block)
60
- end
61
- end
62
- end
63
-
64
- context "with a normal call" do
65
- it "wraps it in a transaction" do
66
- perform
67
- transaction = last_transaction
68
- expect(transaction).to have_action("TestClass#perform")
69
- expect(transaction).to have_namespace("background_job")
70
- expect(transaction).to_not have_error
71
- expect(transaction).to include_event(:name => "perform_job.delayed_job")
72
- expect(transaction).to include_sample_metadata(
73
- "priority" => 1,
74
- "attempts" => 1,
75
- "queue" => "default",
76
- "id" => "123"
77
- )
78
- expect(transaction).to include_params(["argument"])
79
- end
80
-
81
- context "with more complex params" do
82
- let(:args) do
83
- {
84
- :foo => "Foo",
85
- :bar => "Bar"
86
- }
87
- end
88
-
89
- it "adds the more complex arguments" do
90
- perform
91
-
92
- expect(last_transaction).to include_params("foo" => "Foo", "bar" => "Bar")
93
- end
94
-
95
- context "with parameter filtering" do
96
- before do
97
- Appsignal.config = project_fixture_config("production")
98
- Appsignal.config[:filter_parameters] = ["foo"]
99
- end
100
-
101
- it "filters selected arguments" do
102
- perform
103
-
104
- expect(last_transaction).to include_params("foo" => "[FILTERED]", "bar" => "Bar")
105
- end
106
- end
107
- end
108
-
109
- context "with run_at in the future" do
110
- let(:run_at) { Time.parse("2017-01-01 10:01:00UTC") }
111
-
112
- it "reports queue_start with run_at time" do
113
- perform
114
-
115
- expect(last_transaction).to have_queue_start(run_at.to_i * 1000)
116
- end
117
- end
118
-
119
- context "with class method job" do
120
- let(:job_data) do
121
- { :name => "CustomClassMethod.perform", :payload_object => payload_object }
122
- end
123
-
124
- it "wraps it in a transaction using the class method job name" do
125
- perform
126
- expect(last_transaction).to have_action("CustomClassMethod.perform")
127
- end
128
- end
129
-
130
- context "with custom name call" do
131
- before { perform }
132
-
133
- context "with appsignal_name defined" do
134
- context "with payload_object being an object" do
135
- context "with value" do
136
- let(:payload_object) { double(:appsignal_name => "CustomClass#perform") }
137
-
138
- it "wraps it in a transaction using the custom name" do
139
- expect(last_transaction).to have_action("CustomClass#perform")
140
- end
141
- end
142
-
143
- context "with non-String value" do
144
- let(:payload_object) { double(:appsignal_name => Object.new) }
145
-
146
- it "wraps it in a transaction using the original job name" do
147
- expect(last_transaction).to have_action("TestClass#perform")
148
- end
149
- end
150
-
151
- context "with class method name as job" do
152
- let(:payload_object) { double(:appsignal_name => "CustomClassMethod.perform") }
153
-
154
- it "wraps it in a transaction using the custom name" do
155
- perform
156
- expect(last_transaction).to have_action("CustomClassMethod.perform")
157
- end
158
- end
159
- end
160
-
161
- context "with payload_object being a Hash" do
162
- context "with value" do
163
- let(:payload_object) { double(:appsignal_name => "CustomClassHash#perform") }
164
-
165
- it "wraps it in a transaction using the custom name" do
166
- expect(last_transaction).to have_action("CustomClassHash#perform")
167
- end
168
- end
169
-
170
- context "with non-String value" do
171
- let(:payload_object) { double(:appsignal_name => Object.new) }
172
-
173
- it "wraps it in a transaction using the original job name" do
174
- expect(last_transaction).to have_action("TestClass#perform")
175
- end
176
- end
177
-
178
- context "with class method name as job" do
179
- let(:payload_object) { { :appsignal_name => "CustomClassMethod.perform" } }
180
-
181
- it "wraps it in a transaction using the custom name" do
182
- perform
183
- expect(last_transaction).to have_action("CustomClassMethod.perform")
184
- end
185
- end
186
- end
187
-
188
- context "with payload_object acting like a Hash and returning a non-String value" do
189
- class ClassActingAsHash
190
- def self.[](_key)
191
- Object.new
192
- end
193
-
194
- def self.appsignal_name
195
- "ClassActingAsHash#perform"
196
- end
197
- end
198
- let(:payload_object) { ClassActingAsHash }
199
-
200
- # We check for hash values before object values
201
- # this means ClassActingAsHash returns `Object.new` instead
202
- # of `self.appsignal_name`. Since this isn't a valid `String`
203
- # we return the default job name as action name.
204
- it "wraps it in a transaction using the original job name" do
205
- expect(last_transaction).to have_action("TestClass#perform")
206
- end
207
- end
208
- end
209
- end
210
-
211
- context "with only job class name" do
212
- let(:job_data) do
213
- { :name => "Banana", :payload_object => payload_object }
214
- end
215
-
216
- it "appends #perform to the class name" do
217
- perform
218
- expect(last_transaction).to have_action("Banana#perform")
219
- end
220
- end
221
-
222
- if active_job_present?
223
- require "active_job"
224
-
225
- context "when wrapped by ActiveJob" do
226
- let(:payload_object) do
227
- ActiveJob::QueueAdapters::DelayedJobAdapter::JobWrapper.new(
228
- "arguments" => args,
229
- "job_class" => "TestClass",
230
- "job_id" => 123,
231
- "locale" => :en,
232
- "queue_name" => "default"
233
- )
234
- end
235
- let(:job) do
236
- double(
237
- :id => 123,
238
- :name => "ActiveJob::QueueAdapters::DelayedJobAdapter::JobWrapper",
239
- :priority => 1,
240
- :attempts => 1,
241
- :queue => "default",
242
- :created_at => created_at,
243
- :run_at => run_at,
244
- :payload_object => payload_object
245
- )
246
- end
247
- let(:args) { ["activejob_argument"] }
248
-
249
- it "wraps it in a transaction with the correct params" do
250
- perform
251
-
252
- transaction = last_transaction
253
- expect(transaction).to have_namespace("background_job")
254
- expect(transaction).to have_action("TestClass#perform")
255
- expect(transaction).to_not have_error
256
- expect(transaction).to include_event("name" => "perform_job.delayed_job")
257
- expect(transaction).to include_sample_metadata(
258
- "priority" => 1,
259
- "attempts" => 1,
260
- "queue" => "default",
261
- "id" => "123"
262
- )
263
- expect(transaction).to include_params(["activejob_argument"])
264
- end
265
-
266
- context "with more complex params" do
267
- let(:args) do
268
- {
269
- :foo => "Foo",
270
- :bar => "Bar"
271
- }
272
- end
273
-
274
- it "adds the more complex arguments" do
275
- perform
276
- transaction = last_transaction
277
- expect(transaction).to have_action("TestClass#perform")
278
- expect(transaction).to include_params(
279
- "foo" => "Foo",
280
- "bar" => "Bar"
281
- )
282
- end
283
-
284
- context "with parameter filtering" do
285
- before do
286
- Appsignal.config = project_fixture_config("production")
287
- Appsignal.config[:filter_parameters] = ["foo"]
288
- end
289
-
290
- it "filters selected arguments" do
291
- perform
292
- transaction = last_transaction
293
- expect(transaction).to have_action("TestClass#perform")
294
- expect(transaction).to include_params(
295
- "foo" => "[FILTERED]",
296
- "bar" => "Bar"
297
- )
298
- end
299
- end
300
- end
301
-
302
- context "with run_at in the future" do
303
- let(:run_at) { Time.parse("2017-01-01 10:01:00UTC") }
304
-
305
- it "reports queue_start with run_at time" do
306
- perform
307
-
308
- expect(last_transaction).to have_queue_start(run_at.to_i * 1000)
309
- end
310
- end
311
- end
312
- end
313
- end
314
-
315
- context "with an erroring call" do
316
- let(:error) { ExampleException.new("uh oh") }
317
- before do
318
- expect(invoked_block).to receive(:call).and_raise(error)
319
- end
320
-
321
- it "adds the error to the transaction" do
322
- expect do
323
- perform
324
- end.to raise_error(error)
325
-
326
- transaction = last_transaction
327
- expect(transaction).to have_namespace("background_job")
328
- expect(transaction).to have_action("TestClass#perform")
329
- expect(transaction).to have_error("ExampleException", "uh oh")
330
- end
331
- end
332
- end
333
-
334
- describe ".extract_value" do
335
- let(:plugin) { Appsignal::Hooks::DelayedJobPlugin }
336
-
337
- context "for a hash" do
338
- let(:hash) { { :key => "value", :bool_false => false } }
339
-
340
- context "when the key exists" do
341
- subject { plugin.extract_value(hash, :key) }
342
-
343
- it { is_expected.to eq "value" }
344
-
345
- context "when the value is false" do
346
- subject { plugin.extract_value(hash, :bool_false) }
347
-
348
- it { is_expected.to be false }
349
- end
350
- end
351
-
352
- context "when the key does not exist" do
353
- subject { plugin.extract_value(hash, :nonexistent_key) }
354
-
355
- it { is_expected.to be_nil }
356
-
357
- context "with a default value" do
358
- subject { plugin.extract_value(hash, :nonexistent_key, 1) }
359
-
360
- it { is_expected.to eq 1 }
361
- end
362
- end
363
- end
364
-
365
- context "for a struct" do
366
- before :context do
367
- TestStruct = Struct.new(:key)
368
- end
369
- let(:struct) { TestStruct.new("value") }
370
-
371
- context "when the key exists" do
372
- subject { plugin.extract_value(struct, :key) }
373
-
374
- it { is_expected.to eq "value" }
375
- end
376
-
377
- context "when the key does not exist" do
378
- subject { plugin.extract_value(struct, :nonexistent_key) }
379
-
380
- it { is_expected.to be_nil }
381
-
382
- context "with a default value" do
383
- subject { plugin.extract_value(struct, :nonexistent_key, 1) }
384
-
385
- it { is_expected.to eq 1 }
386
- end
387
- end
388
- end
389
-
390
- context "for a struct with a method" do
391
- before :context do
392
- class TestStructClass < Struct.new(:id) # rubocop:disable Style/StructInheritance
393
- def appsignal_name
394
- "TestStruct#perform"
395
- end
396
-
397
- def bool_false
398
- false
399
- end
400
- end
401
- end
402
- let(:struct) { TestStructClass.new("id") }
403
-
404
- context "when the Struct responds to a method" do
405
- subject { plugin.extract_value(struct, :appsignal_name) }
406
-
407
- it "returns the method value" do
408
- is_expected.to eq "TestStruct#perform"
409
- end
410
-
411
- context "when the value is false" do
412
- subject { plugin.extract_value(struct, :bool_false) }
413
-
414
- it "returns the method value" do
415
- is_expected.to be false
416
- end
417
- end
418
- end
419
-
420
- context "when the key does not exist" do
421
- subject { plugin.extract_value(struct, :nonexistent_key) }
422
-
423
- context "without a method with the same name" do
424
- it "returns nil" do
425
- is_expected.to be_nil
426
- end
427
- end
428
-
429
- context "with a default value" do
430
- let(:default_value) { :my_default_value }
431
- subject { plugin.extract_value(struct, :nonexistent_key, default_value) }
432
-
433
- it "returns the default value" do
434
- is_expected.to eq default_value
435
- end
436
- end
437
- end
438
- end
439
-
440
- context "for an object" do
441
- let(:object) { double(:existing_method => "value") }
442
-
443
- context "when the method exists" do
444
- subject { plugin.extract_value(object, :existing_method) }
445
-
446
- it { is_expected.to eq "value" }
447
- end
448
-
449
- context "when the method does not exist" do
450
- subject { plugin.extract_value(object, :nonexistent_method) }
451
-
452
- it { is_expected.to be_nil }
453
-
454
- context "and there is a default value" do
455
- subject { plugin.extract_value(object, :nonexistent_method, 1) }
456
-
457
- it { is_expected.to eq 1 }
458
- end
459
- end
460
- end
461
-
462
- context "when we need to call to_s on the value" do
463
- let(:object) { double(:existing_method => 1) }
464
-
465
- subject { plugin.extract_value(object, :existing_method, nil, true) }
466
-
467
- it { is_expected.to eq "1" }
468
- end
27
+ expect(::Delayed::Worker.plugins).to include(Appsignal::Integrations::DelayedJobPlugin)
469
28
  end
470
29
  end
471
30
 
@@ -1,41 +1,89 @@
1
1
  require "rake"
2
2
 
3
3
  describe Appsignal::Hooks::RakeHook do
4
+ let(:helper) { Appsignal::Integrations::RakeIntegrationHelper }
4
5
  let(:task) { Rake::Task.new("task:name", Rake::Application.new) }
5
6
  let(:arguments) { Rake::TaskArguments.new(["foo"], ["bar"]) }
6
- let(:generic_request) { Appsignal::Transaction::GenericRequest.new({}) }
7
- before(:context) { start_agent }
7
+ before do
8
+ start_agent
9
+ allow(Kernel).to receive(:at_exit)
10
+ end
11
+ around { |example| keep_transactions { example.run } }
12
+ after do
13
+ if helper.instance_variable_defined?(:@register_at_exit_hook)
14
+ helper.remove_instance_variable(:@register_at_exit_hook)
15
+ end
16
+ end
17
+
18
+ def expect_to_not_have_registered_at_exit_hook
19
+ expect(Kernel).to_not have_received(:at_exit)
20
+ end
21
+
22
+ def expect_to_have_registered_at_exit_hook
23
+ expect(Kernel).to have_received(:at_exit)
24
+ end
8
25
 
9
26
  describe "#execute" do
10
27
  context "without error" do
11
- before { expect(Appsignal).to_not receive(:stop) }
12
-
13
28
  def perform
14
29
  task.execute(arguments)
15
30
  end
16
31
 
17
- it "creates no transaction" do
18
- expect { perform }.to_not(change { created_transactions.count })
32
+ context "with :enable_rake_performance_instrumentation == false" do
33
+ before do
34
+ Appsignal.config[:enable_rake_performance_instrumentation] = false
35
+ end
36
+
37
+ it "creates no transaction" do
38
+ expect { perform }.to_not(change { created_transactions.count })
39
+ end
40
+
41
+ it "calls the original task" do
42
+ expect(perform).to eq([])
43
+ end
44
+
45
+ it "does not register an at_exit hook" do
46
+ perform
47
+ expect_to_not_have_registered_at_exit_hook
48
+ end
19
49
  end
20
50
 
21
- it "calls the original task" do
22
- expect(perform).to eq([])
51
+ context "with :enable_rake_performance_instrumentation == true" do
52
+ before do
53
+ Appsignal.config[:enable_rake_performance_instrumentation] = true
54
+ end
55
+
56
+ it "creates a transaction" do
57
+ expect { perform }.to(change { created_transactions.count }.by(1))
58
+
59
+ transaction = last_transaction
60
+ expect(transaction).to have_id
61
+ expect(transaction).to have_namespace(Appsignal::Transaction::BACKGROUND_JOB)
62
+ expect(transaction).to have_action("task:name")
63
+ expect(transaction).to_not have_error
64
+ expect(transaction).to include_params("foo" => "bar")
65
+ expect(transaction).to include_event("name" => "task.rake")
66
+ expect(transaction).to be_completed
67
+ end
68
+
69
+ it "calls the original task" do
70
+ expect(perform).to eq([])
71
+ end
72
+
73
+ it "registers an at_exit hook" do
74
+ perform
75
+ expect_to_have_registered_at_exit_hook
76
+ end
23
77
  end
24
78
  end
25
79
 
26
80
  context "with error" do
27
- let(:error) { ExampleException }
28
81
  before do
29
- task.enhance { raise error, "my error message" }
30
- # We don't call `and_call_original` here as we don't want AppSignal to
31
- # stop and start for every spec.
32
- expect(Appsignal).to receive(:stop).with("rake")
82
+ task.enhance { raise ExampleException, "error message" }
33
83
  end
34
84
 
35
85
  def perform
36
- keep_transactions do
37
- expect { task.execute(arguments) }.to raise_error(error)
38
- end
86
+ expect { task.execute(arguments) }.to raise_error(ExampleException, "error message")
39
87
  end
40
88
 
41
89
  it "creates a background job transaction" do
@@ -45,11 +93,16 @@ describe Appsignal::Hooks::RakeHook do
45
93
  expect(transaction).to have_id
46
94
  expect(transaction).to have_namespace(Appsignal::Transaction::BACKGROUND_JOB)
47
95
  expect(transaction).to have_action("task:name")
48
- expect(transaction).to have_error("ExampleException", "my error message")
96
+ expect(transaction).to have_error("ExampleException", "error message")
49
97
  expect(transaction).to include_params("foo" => "bar")
50
98
  expect(transaction).to be_completed
51
99
  end
52
100
 
101
+ it "registers an at_exit hook" do
102
+ perform
103
+ expect_to_have_registered_at_exit_hook
104
+ end
105
+
53
106
  context "when first argument is not a `Rake::TaskArguments`" do
54
107
  let(:arguments) { nil }
55
108
 
@@ -62,3 +115,33 @@ describe Appsignal::Hooks::RakeHook do
62
115
  end
63
116
  end
64
117
  end
118
+
119
+ describe "Appsignal::Integrations::RakeIntegrationHelper" do
120
+ let(:helper) { Appsignal::Integrations::RakeIntegrationHelper }
121
+ describe ".register_at_exit_hook" do
122
+ before do
123
+ start_agent
124
+ allow(Appsignal).to receive(:stop)
125
+ end
126
+
127
+ it "registers the at_exit hook only once" do
128
+ allow(Kernel).to receive(:at_exit)
129
+ helper.register_at_exit_hook
130
+ helper.register_at_exit_hook
131
+ expect(Kernel).to have_received(:at_exit).once
132
+ end
133
+ end
134
+
135
+ describe ".at_exit_hook" do
136
+ let(:helper) { Appsignal::Integrations::RakeIntegrationHelper }
137
+ before do
138
+ start_agent
139
+ allow(Appsignal).to receive(:stop)
140
+ end
141
+
142
+ it "calls Appsignal.stop" do
143
+ helper.at_exit_hook
144
+ expect(Appsignal).to have_received(:stop).with("rake")
145
+ end
146
+ end
147
+ end