appsignal 2.11.0.alpha.1-java → 2.11.0.beta.4-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 (49) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +3 -0
  3. data/.semaphore/semaphore.yml +75 -61
  4. data/CHANGELOG.md +21 -1
  5. data/build_matrix.yml +13 -7
  6. data/ext/agent.yml +19 -19
  7. data/gemfiles/padrino.gemfile +2 -2
  8. data/gemfiles/rails-4.2.gemfile +9 -2
  9. data/gemfiles/rails-5.0.gemfile +1 -0
  10. data/gemfiles/rails-5.1.gemfile +1 -0
  11. data/gemfiles/rails-5.2.gemfile +1 -0
  12. data/gemfiles/rails-6.0.gemfile +1 -0
  13. data/gemfiles/resque-1.gemfile +7 -0
  14. data/gemfiles/{resque.gemfile → resque-2.gemfile} +1 -1
  15. data/lib/appsignal/hooks.rb +2 -0
  16. data/lib/appsignal/hooks/active_job.rb +114 -0
  17. data/lib/appsignal/hooks/puma.rb +2 -58
  18. data/lib/appsignal/hooks/resque.rb +60 -0
  19. data/lib/appsignal/hooks/sidekiq.rb +19 -192
  20. data/lib/appsignal/integrations/delayed_job_plugin.rb +1 -1
  21. data/lib/appsignal/integrations/que.rb +1 -1
  22. data/lib/appsignal/integrations/resque.rb +9 -12
  23. data/lib/appsignal/integrations/resque_active_job.rb +9 -32
  24. data/lib/appsignal/probes/puma.rb +61 -0
  25. data/lib/appsignal/probes/sidekiq.rb +102 -0
  26. data/lib/appsignal/rack/js_exception_catcher.rb +5 -2
  27. data/lib/appsignal/transaction.rb +10 -0
  28. data/lib/appsignal/utils/deprecation_message.rb +5 -1
  29. data/lib/appsignal/version.rb +1 -1
  30. data/lib/puma/plugin/appsignal.rb +2 -1
  31. data/spec/lib/appsignal/hooks/activejob_spec.rb +548 -0
  32. data/spec/lib/appsignal/hooks/delayed_job_spec.rb +3 -14
  33. data/spec/lib/appsignal/hooks/puma_spec.rb +2 -181
  34. data/spec/lib/appsignal/hooks/resque_spec.rb +185 -0
  35. data/spec/lib/appsignal/hooks/sidekiq_spec.rb +297 -549
  36. data/spec/lib/appsignal/integrations/padrino_spec.rb +1 -1
  37. data/spec/lib/appsignal/integrations/que_spec.rb +25 -6
  38. data/spec/lib/appsignal/integrations/resque_active_job_spec.rb +20 -179
  39. data/spec/lib/appsignal/integrations/resque_spec.rb +20 -85
  40. data/spec/lib/appsignal/probes/puma_spec.rb +180 -0
  41. data/spec/lib/appsignal/probes/sidekiq_spec.rb +204 -0
  42. data/spec/lib/appsignal/rack/js_exception_catcher_spec.rb +9 -4
  43. data/spec/lib/appsignal/transaction_spec.rb +5 -7
  44. data/spec/lib/puma/appsignal_spec.rb +1 -1
  45. data/spec/support/helpers/action_mailer_helpers.rb +25 -0
  46. data/spec/support/helpers/dependency_helper.rb +9 -2
  47. data/spec/support/helpers/transaction_helpers.rb +6 -0
  48. data/spec/support/stubs/sidekiq/api.rb +2 -2
  49. metadata +18 -3
@@ -227,25 +227,14 @@ describe Appsignal::Hooks::DelayedJobHook do
227
227
  end
228
228
  end
229
229
 
230
- context "without job name" do
231
- let(:job_data) do
232
- { :name => "", :payload_object => payload_object }
233
- end
234
-
235
- it "wraps it in a transaction using the class method job name" do
236
- perform
237
- expect(last_transaction.to_h["action"]).to eql("unknown")
238
- end
239
- end
240
-
241
- context "with invalid job name" do
230
+ context "with only job class name" do
242
231
  let(:job_data) do
243
232
  { :name => "Banana", :payload_object => payload_object }
244
233
  end
245
234
 
246
- it "wraps it in a transaction using the class method job name" do
235
+ it "appends #perform to the class name" do
247
236
  perform
248
- expect(last_transaction.to_h["action"]).to eql("unknown")
237
+ expect(last_transaction.to_h["action"]).to eql("Banana#perform")
249
238
  end
250
239
  end
251
240
 
@@ -55,7 +55,7 @@ describe Appsignal::Hooks::PumaHook do
55
55
 
56
56
  Appsignal::Hooks::PumaHook.new.install
57
57
  probe = Appsignal::Minutely.probes[:puma]
58
- expect(probe).to eql(Appsignal::Hooks::PumaProbe)
58
+ expect(probe).to eql(Appsignal::Probes::PumaProbe)
59
59
  end
60
60
  end
61
61
  end
@@ -101,7 +101,7 @@ describe Appsignal::Hooks::PumaHook do
101
101
 
102
102
  Appsignal::Hooks::PumaHook.new.install
103
103
  probe = Appsignal::Minutely.probes[:puma]
104
- expect(probe).to eql(Appsignal::Hooks::PumaProbe)
104
+ expect(probe).to eql(Appsignal::Probes::PumaProbe)
105
105
  end
106
106
  end
107
107
  end
@@ -116,182 +116,3 @@ describe Appsignal::Hooks::PumaHook do
116
116
  end
117
117
  end
118
118
  end
119
-
120
- describe Appsignal::Hooks::PumaProbe do
121
- before(:context) do
122
- Appsignal.config = project_fixture_config
123
- end
124
- after(:context) do
125
- Appsignal.config = nil
126
- end
127
-
128
- let(:probe) { Appsignal::Hooks::PumaProbe.new }
129
-
130
- describe "hostname" do
131
- it "returns the socket hostname" do
132
- expect(probe.send(:hostname)).to eql(Socket.gethostname)
133
- end
134
-
135
- context "with overridden hostname" do
136
- around do |sample|
137
- Appsignal.config[:hostname] = "frontend1"
138
- sample.run
139
- Appsignal.config[:hostname] = nil
140
- end
141
- it "returns the configured host" do
142
- expect(probe.send(:hostname)).to eql("frontend1")
143
- end
144
- end
145
- end
146
-
147
- describe "#call" do
148
- let(:expected_default_tags) { { :hostname => Socket.gethostname } }
149
-
150
- context "with multiple worker stats" do
151
- before(:context) do
152
- class Puma
153
- def self.stats
154
- {
155
- "workers" => 2,
156
- "booted_workers" => 2,
157
- "old_workers" => 0,
158
- "worker_status" => [
159
- {
160
- "last_status" => {
161
- "backlog" => 0,
162
- "running" => 5,
163
- "pool_capacity" => 5,
164
- "max_threads" => 5
165
- }
166
- },
167
- {
168
- "last_status" => {
169
- "backlog" => 0,
170
- "running" => 5,
171
- "pool_capacity" => 5,
172
- "max_threads" => 5
173
- }
174
- }
175
- ]
176
- }.to_json
177
- end
178
- end
179
- end
180
- after(:context) { Object.send(:remove_const, :Puma) }
181
-
182
- it "calls `puma_gauge` with the (summed) worker metrics" do
183
- expect_gauge(:workers, 2, :type => :count)
184
- expect_gauge(:workers, 2, :type => :booted)
185
- expect_gauge(:workers, 0, :type => :old)
186
-
187
- expect_gauge(:connection_backlog, 0)
188
- expect_gauge(:pool_capacity, 10)
189
- expect_gauge(:threads, 10, :type => :running)
190
- expect_gauge(:threads, 10, :type => :max)
191
-
192
- probe.call
193
- end
194
- end
195
-
196
- context "with single worker stats" do
197
- before(:context) do
198
- class Puma
199
- def self.stats
200
- {
201
- "backlog" => 0,
202
- "running" => 5,
203
- "pool_capacity" => 5,
204
- "max_threads" => 5
205
- }.to_json
206
- end
207
- end
208
- end
209
- after(:context) { Object.send(:remove_const, :Puma) }
210
-
211
- it "calls `puma_gauge` with the (summed) worker metrics" do
212
- expect_gauge(:connection_backlog, 0)
213
- expect_gauge(:pool_capacity, 5)
214
- expect_gauge(:threads, 5, :type => :running)
215
- expect_gauge(:threads, 5, :type => :max)
216
- probe.call
217
- end
218
- end
219
-
220
- context "without stats" do
221
- before(:context) do
222
- class Puma
223
- def self.stats
224
- end
225
- end
226
- end
227
- after(:context) { Object.send(:remove_const, :Puma) }
228
-
229
- context "when it returns nil" do
230
- it "does not track metrics" do
231
- expect(probe).to_not receive(:puma_gauge)
232
- probe.call
233
- end
234
- end
235
-
236
- # Puma.stats raises a NoMethodError on a nil object on the first call.
237
- context "when it returns a NoMethodError on the first call" do
238
- let(:log) { StringIO.new }
239
-
240
- it "ignores the first call and tracks the second call" do
241
- use_logger_with log do
242
- expect(Puma).to receive(:stats)
243
- .and_raise(NoMethodError.new("undefined method `stats' for nil:NilClass"))
244
- probe.call
245
-
246
- expect(Puma).to receive(:stats).and_return({
247
- "backlog" => 1,
248
- "running" => 5,
249
- "pool_capacity" => 4,
250
- "max_threads" => 6
251
- }.to_json)
252
-
253
- expect_gauge(:connection_backlog, 1)
254
- expect_gauge(:pool_capacity, 4)
255
- expect_gauge(:threads, 5, :type => :running)
256
- expect_gauge(:threads, 6, :type => :max)
257
- probe.call
258
- end
259
-
260
- expect(log_contents(log)).to_not contains_log(:error, "Error in minutely probe 'puma'")
261
- end
262
- end
263
-
264
- context "when it does not have a complete stats payload" do
265
- let(:log) { StringIO.new }
266
-
267
- it "tracks whatever metrics we do have" do
268
- use_logger_with log do
269
- expect(Puma).to receive(:stats).and_return({
270
- "backlog" => 1,
271
- "running" => 5
272
- }.to_json)
273
-
274
- expect_gauge(:connection_backlog, 1)
275
- expect_no_gauge(:pool_capacity)
276
- expect_gauge(:threads, 5, :type => :running)
277
- expect_no_gauge(:threads, :type => :max)
278
- probe.call
279
- end
280
-
281
- expect(log_contents(log)).to_not contains_log(:error, "Error in minutely probe 'puma'")
282
- end
283
- end
284
- end
285
-
286
- def expect_gauge(key, value, tags = {})
287
- expect(Appsignal).to receive(:set_gauge)
288
- .with("puma_#{key}", value, expected_default_tags.merge(tags))
289
- .and_call_original
290
- end
291
-
292
- def expect_no_gauge(key, tags = {})
293
- expect(Appsignal).to_not receive(:set_gauge)
294
- .with("puma_#{key}", anything, tags)
295
- end
296
- end
297
- end
@@ -0,0 +1,185 @@
1
+ describe Appsignal::Hooks::ResqueHook do
2
+ describe "#dependency_present?" do
3
+ subject { described_class.new.dependencies_present? }
4
+
5
+ context "when Resque is loaded" do
6
+ before { stub_const "Resque", 1 }
7
+
8
+ it { is_expected.to be_truthy }
9
+ end
10
+
11
+ context "when Resque is not loaded" do
12
+ before { hide_const "Resque" }
13
+
14
+ it { is_expected.to be_falsy }
15
+ end
16
+ end
17
+
18
+ if DependencyHelper.resque_present?
19
+ describe "#install" do
20
+ def perform_job(klass, options = {})
21
+ payload = { "class" => klass.to_s }.merge(options)
22
+ job = ::Resque::Job.new(queue, payload)
23
+ keep_transactions { job.perform }
24
+ end
25
+
26
+ let(:queue) { "default" }
27
+ let(:namespace) { Appsignal::Transaction::BACKGROUND_JOB }
28
+ before do
29
+ start_agent
30
+
31
+ class ResqueTestJob
32
+ def self.perform(*_args)
33
+ end
34
+ end
35
+
36
+ class ResqueErrorTestJob
37
+ def self.perform
38
+ raise "resque job error"
39
+ end
40
+ end
41
+
42
+ expect(Appsignal).to receive(:stop) # Resque calls stop after every job
43
+ end
44
+ around do |example|
45
+ keep_transactions { example.run }
46
+ end
47
+ after do
48
+ Object.send(:remove_const, :ResqueTestJob)
49
+ Object.send(:remove_const, :ResqueErrorTestJob)
50
+ end
51
+
52
+ it "tracks a transaction on perform" do
53
+ perform_job(ResqueTestJob)
54
+
55
+ transaction = last_transaction
56
+ transaction_hash = transaction.to_h
57
+ expect(transaction_hash).to include(
58
+ "id" => kind_of(String),
59
+ "action" => "ResqueTestJob#perform",
60
+ "error" => nil,
61
+ "namespace" => namespace,
62
+ "metadata" => {},
63
+ "sample_data" => { "tags" => { "queue" => queue } }
64
+ )
65
+ expect(transaction_hash["events"].map { |e| e["name"] })
66
+ .to eql(["perform.resque"])
67
+ end
68
+
69
+ context "with error" do
70
+ it "tracks the error on the transaction" do
71
+ expect do
72
+ perform_job(ResqueErrorTestJob)
73
+ end.to raise_error(RuntimeError, "resque job error")
74
+
75
+ transaction = last_transaction
76
+ transaction_hash = transaction.to_h
77
+ expect(transaction_hash).to include(
78
+ "id" => kind_of(String),
79
+ "action" => "ResqueErrorTestJob#perform",
80
+ "error" => {
81
+ "name" => "RuntimeError",
82
+ "message" => "resque job error",
83
+ "backtrace" => kind_of(String)
84
+ },
85
+ "namespace" => namespace,
86
+ "metadata" => {},
87
+ "sample_data" => { "tags" => { "queue" => queue } }
88
+ )
89
+ end
90
+ end
91
+
92
+ context "with arguments" do
93
+ before do
94
+ Appsignal.config = project_fixture_config("production")
95
+ Appsignal.config[:filter_parameters] = ["foo"]
96
+ end
97
+
98
+ it "filters out configured arguments" do
99
+ perform_job(
100
+ ResqueTestJob,
101
+ "args" => [
102
+ "foo",
103
+ {
104
+ "foo" => "secret",
105
+ "bar" => "Bar",
106
+ "baz" => { "1" => "foo" }
107
+ }
108
+ ]
109
+ )
110
+
111
+ transaction = last_transaction
112
+ transaction_hash = transaction.to_h
113
+ expect(transaction_hash).to include(
114
+ "id" => kind_of(String),
115
+ "action" => "ResqueTestJob#perform",
116
+ "error" => nil,
117
+ "namespace" => namespace,
118
+ "metadata" => {},
119
+ "sample_data" => {
120
+ "tags" => { "queue" => queue },
121
+ "params" => [
122
+ "foo",
123
+ {
124
+ "foo" => "[FILTERED]",
125
+ "bar" => "Bar",
126
+ "baz" => { "1" => "foo" }
127
+ }
128
+ ]
129
+ }
130
+ )
131
+ end
132
+ end
133
+
134
+ context "with active job" do
135
+ before do
136
+ module ActiveJobMock
137
+ module QueueAdapters
138
+ module ResqueAdapter
139
+ module JobWrapper
140
+ class << self
141
+ def perform(job_data)
142
+ # Basic ActiveJob stub for this test.
143
+ # I haven't found a way to run Resque in a testing mode.
144
+ Appsignal.set_action(job_data["job_class"])
145
+ end
146
+ end
147
+ end
148
+ end
149
+ end
150
+ end
151
+
152
+ stub_const "ActiveJob", ActiveJobMock
153
+ end
154
+ after { Object.send(:remove_const, :ActiveJobMock) }
155
+
156
+ it "does not set arguments but lets the ActiveJob intergration handle it" do
157
+ perform_job(
158
+ ResqueTestJob,
159
+ "class" => "ActiveJob::QueueAdapters::ResqueAdapter::JobWrapper",
160
+ "args" => [
161
+ {
162
+ "job_class" => "ResqueTestJobByActiveJob#perform",
163
+ "arguments" => ["an argument", "second argument"]
164
+ }
165
+ ]
166
+ )
167
+
168
+ transaction = last_transaction
169
+ transaction_hash = transaction.to_h
170
+ expect(transaction_hash).to include(
171
+ "id" => kind_of(String),
172
+ "action" => "ResqueTestJobByActiveJob#perform",
173
+ "error" => nil,
174
+ "namespace" => namespace,
175
+ "metadata" => {},
176
+ "sample_data" => {
177
+ "tags" => { "queue" => queue }
178
+ # Params will be set by the ActiveJob integration
179
+ }
180
+ )
181
+ end
182
+ end
183
+ end
184
+ end
185
+ end
@@ -1,3 +1,54 @@
1
+ describe Appsignal::Hooks::SidekiqHook do
2
+ describe "#dependencies_present?" do
3
+ subject { described_class.new.dependencies_present? }
4
+
5
+ context "when Sidekiq constant is found" do
6
+ before { stub_const "Sidekiq", Class.new }
7
+
8
+ it { is_expected.to be_truthy }
9
+ end
10
+
11
+ context "when Sidekiq constant is not found" do
12
+ before { hide_const "Sidekiq" }
13
+
14
+ it { is_expected.to be_falsy }
15
+ end
16
+ end
17
+
18
+ describe "#install" do
19
+ class SidekiqMiddlewareMock < Set
20
+ def exists?(middleware)
21
+ include?(middleware)
22
+ end
23
+ end
24
+ module SidekiqMock
25
+ def self.middlewares
26
+ @middlewares ||= SidekiqMiddlewareMock.new
27
+ end
28
+
29
+ def self.configure_server
30
+ yield self
31
+ end
32
+
33
+ def self.server_middleware
34
+ yield middlewares if block_given?
35
+ middlewares
36
+ end
37
+ end
38
+
39
+ before do
40
+ Appsignal.config = project_fixture_config
41
+ stub_const "Sidekiq", SidekiqMock
42
+ end
43
+
44
+ it "adds the AppSignal SidekiqPlugin to the Sidekiq middleware chain" do
45
+ described_class.new.install
46
+
47
+ expect(Sidekiq.server_middleware.exists?(Appsignal::Hooks::SidekiqPlugin)).to be(true)
48
+ end
49
+ end
50
+ end
51
+
1
52
  describe Appsignal::Hooks::SidekiqPlugin, :with_yaml_parse_error => false do
2
53
  class DelayedTestClass; end
3
54
 
@@ -25,9 +76,10 @@ describe Appsignal::Hooks::SidekiqPlugin, :with_yaml_parse_error => false do
25
76
  ]
26
77
  end
27
78
  let(:job_class) { "TestClass" }
79
+ let(:jid) { "b4a577edbccf1d805744efa9" }
28
80
  let(:item) do
29
81
  {
30
- "jid" => "b4a577edbccf1d805744efa9",
82
+ "jid" => jid,
31
83
  "class" => job_class,
32
84
  "retry_count" => 0,
33
85
  "queue" => "default",
@@ -48,369 +100,132 @@ describe Appsignal::Hooks::SidekiqPlugin, :with_yaml_parse_error => false do
48
100
  expect(log_contents(log)).to_not contains_log(:warn, "Unable to load YAML")
49
101
  end
50
102
 
51
- shared_examples "sidekiq metadata" do
52
- describe "internal Sidekiq job values" do
53
- it "does not save internal Sidekiq values as metadata on transaction" do
54
- perform_job
103
+ describe "internal Sidekiq job values" do
104
+ it "does not save internal Sidekiq values as metadata on transaction" do
105
+ perform_job
55
106
 
56
- transaction_hash = transaction.to_h
57
- expect(transaction_hash["metadata"].keys)
58
- .to_not include(*Appsignal::Hooks::SidekiqPlugin::JOB_KEYS)
59
- end
107
+ transaction_hash = transaction.to_h
108
+ expect(transaction_hash["metadata"].keys)
109
+ .to_not include(*Appsignal::Hooks::SidekiqPlugin::EXCLUDED_JOB_KEYS)
60
110
  end
111
+ end
61
112
 
62
- context "with parameter filtering" do
63
- before do
64
- Appsignal.config = project_fixture_config("production")
65
- Appsignal.config[:filter_parameters] = ["foo"]
66
- end
67
-
68
- it "filters selected arguments" do
69
- perform_job
70
-
71
- transaction_hash = transaction.to_h
72
- expect(transaction_hash["sample_data"]).to include(
73
- "params" => [
74
- "foo",
75
- {
76
- "foo" => "[FILTERED]",
77
- "bar" => "Bar",
78
- "baz" => { "1" => "foo" }
79
- }
80
- ]
81
- )
82
- end
113
+ context "with parameter filtering" do
114
+ before do
115
+ Appsignal.config = project_fixture_config("production")
116
+ Appsignal.config[:filter_parameters] = ["foo"]
83
117
  end
84
118
 
85
- context "with encrypted arguments" do
86
- before do
87
- item["encrypt"] = true
88
- item["args"] << "super secret value" # Last argument will be replaced
89
- end
90
-
91
- it "replaces the last argument (the secret bag) with an [encrypted data] string" do
92
- perform_job
119
+ it "filters selected arguments" do
120
+ perform_job
93
121
 
94
- transaction_hash = transaction.to_h
95
- expect(transaction_hash["sample_data"]).to include(
96
- "params" => expected_args << "[encrypted data]"
97
- )
98
- end
122
+ transaction_hash = transaction.to_h
123
+ expect(transaction_hash["sample_data"]).to include(
124
+ "params" => [
125
+ "foo",
126
+ {
127
+ "foo" => "[FILTERED]",
128
+ "bar" => "Bar",
129
+ "baz" => { "1" => "foo" }
130
+ }
131
+ ]
132
+ )
99
133
  end
134
+ end
100
135
 
101
- context "when using the Sidekiq delayed extension" do
102
- let(:item) do
103
- {
104
- "jid" => "efb140489485999d32b5504c",
105
- "class" => "Sidekiq::Extensions::DelayedClass",
106
- "queue" => "default",
107
- "args" => [
108
- "---\n- !ruby/class 'DelayedTestClass'\n- :foo_method\n- - :bar: baz\n"
109
- ],
110
- "retry" => true,
111
- "created_at" => Time.parse("2001-01-01 10:00:00UTC").to_f,
112
- "enqueued_at" => Time.parse("2001-01-01 10:00:00UTC").to_f,
113
- "extra" => "data"
114
- }
115
- end
116
-
117
- it "uses the delayed class and method name for the action" do
118
- perform_job
119
-
120
- transaction_hash = transaction.to_h
121
- expect(transaction_hash["action"]).to eq("DelayedTestClass.foo_method")
122
- expect(transaction_hash["sample_data"]).to include(
123
- "params" => ["bar" => "baz"]
124
- )
125
- end
126
-
127
- context "when job arguments is a malformed YAML object", :with_yaml_parse_error => true do
128
- before { item["args"] = [] }
129
-
130
- it "logs a warning and uses the default argument" do
131
- perform_job
132
-
133
- transaction_hash = transaction.to_h
134
- expect(transaction_hash["action"]).to eq("Sidekiq::Extensions::DelayedClass#perform")
135
- expect(transaction_hash["sample_data"]).to include("params" => [])
136
- expect(log_contents(log)).to contains_log(:warn, "Unable to load YAML")
137
- end
138
- end
136
+ context "with encrypted arguments" do
137
+ before do
138
+ item["encrypt"] = true
139
+ item["args"] << "super secret value" # Last argument will be replaced
139
140
  end
140
141
 
141
- context "when using the Sidekiq ActiveRecord instance delayed extension" do
142
- let(:item) do
143
- {
144
- "jid" => "efb140489485999d32b5504c",
145
- "class" => "Sidekiq::Extensions::DelayedModel",
146
- "queue" => "default",
147
- "args" => [
148
- "---\n- !ruby/object:DelayedTestClass {}\n- :foo_method\n- - :bar: :baz\n"
149
- ],
150
- "retry" => true,
151
- "created_at" => Time.parse("2001-01-01 10:00:00UTC").to_f,
152
- "enqueued_at" => Time.parse("2001-01-01 10:00:00UTC").to_f,
153
- "extra" => "data"
154
- }
155
- end
156
-
157
- it "uses the delayed class and method name for the action" do
158
- perform_job
142
+ it "replaces the last argument (the secret bag) with an [encrypted data] string" do
143
+ perform_job
159
144
 
160
- transaction_hash = transaction.to_h
161
- expect(transaction_hash["action"]).to eq("DelayedTestClass#foo_method")
162
- expect(transaction_hash["sample_data"]).to include(
163
- "params" => ["bar" => "baz"]
164
- )
165
- end
145
+ transaction_hash = transaction.to_h
146
+ expect(transaction_hash["sample_data"]).to include(
147
+ "params" => expected_args << "[encrypted data]"
148
+ )
149
+ end
150
+ end
166
151
 
167
- context "when job arguments is a malformed YAML object", :with_yaml_parse_error => true do
168
- before { item["args"] = [] }
152
+ context "when using the Sidekiq delayed extension" do
153
+ let(:item) do
154
+ {
155
+ "jid" => jid,
156
+ "class" => "Sidekiq::Extensions::DelayedClass",
157
+ "queue" => "default",
158
+ "args" => [
159
+ "---\n- !ruby/class 'DelayedTestClass'\n- :foo_method\n- - :bar: baz\n"
160
+ ],
161
+ "retry" => true,
162
+ "created_at" => Time.parse("2001-01-01 10:00:00UTC").to_f,
163
+ "enqueued_at" => Time.parse("2001-01-01 10:00:00UTC").to_f,
164
+ "extra" => "data"
165
+ }
166
+ end
169
167
 
170
- it "logs a warning and uses the default argument" do
171
- perform_job
168
+ it "uses the delayed class and method name for the action" do
169
+ perform_job
172
170
 
173
- transaction_hash = transaction.to_h
174
- expect(transaction_hash["action"]).to eq("Sidekiq::Extensions::DelayedModel#perform")
175
- expect(transaction_hash["sample_data"]).to include("params" => [])
176
- expect(log_contents(log)).to contains_log(:warn, "Unable to load YAML")
177
- end
178
- end
171
+ transaction_hash = transaction.to_h
172
+ expect(transaction_hash["action"]).to eq("DelayedTestClass.foo_method")
173
+ expect(transaction_hash["sample_data"]).to include(
174
+ "params" => ["bar" => "baz"]
175
+ )
179
176
  end
180
177
 
181
- context "when using ActiveJob" do
182
- let(:item) do
183
- {
184
- "class" => "ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper",
185
- "wrapped" => "ActiveJobTestClass",
186
- "queue" => "default",
187
- "args" => [{
188
- "job_class" => "ActiveJobTestJob",
189
- "job_id" => "23e79d48-6966-40d0-b2d4-f7938463a263",
190
- "queue_name" => "default",
191
- "arguments" => [
192
- "foo", { "foo" => "Foo", "bar" => "Bar", "baz" => { 1 => :bar } }
193
- ]
194
- }],
195
- "retry" => true,
196
- "jid" => "efb140489485999d32b5504c",
197
- "created_at" => Time.parse("2001-01-01 10:00:00UTC").to_f,
198
- "enqueued_at" => Time.parse("2001-01-01 10:00:00UTC").to_f
199
- }
200
- end
178
+ context "when job arguments is a malformed YAML object", :with_yaml_parse_error => true do
179
+ before { item["args"] = [] }
201
180
 
202
- it "creates a transaction with events" do
181
+ it "logs a warning and uses the default argument" do
203
182
  perform_job
204
183
 
205
184
  transaction_hash = transaction.to_h
206
- expect(transaction_hash).to include(
207
- "id" => kind_of(String),
208
- "action" => "ActiveJobTestClass#perform",
209
- "error" => nil,
210
- "namespace" => namespace,
211
- "metadata" => {
212
- "queue" => "default"
213
- },
214
- "sample_data" => {
215
- "environment" => {},
216
- "params" => [
217
- "foo",
218
- {
219
- "foo" => "Foo",
220
- "bar" => "Bar",
221
- "baz" => { "1" => "bar" }
222
- }
223
- ],
224
- "tags" => {}
225
- }
226
- )
227
- # TODO: Not available in transaction.to_h yet.
228
- # https://github.com/appsignal/appsignal-agent/issues/293
229
- expect(transaction.request.env).to eq(
230
- :queue_start => Time.parse("2001-01-01 10:00:00UTC").to_f
231
- )
232
- expect_transaction_to_have_sidekiq_event(transaction_hash)
233
- end
234
-
235
- context "with ActionMailer job" do
236
- let(:item) do
237
- {
238
- "class" => "ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper",
239
- "wrapped" => "ActionMailer::DeliveryJob",
240
- "queue" => "default",
241
- "args" => [{
242
- "job_class" => "ActiveMailerTestJob",
243
- "job_id" => "23e79d48-6966-40d0-b2d4-f7938463a263",
244
- "queue_name" => "default",
245
- "arguments" => [
246
- "MailerClass", "mailer_method", "deliver_now",
247
- "foo", { "foo" => "Foo", "bar" => "Bar", "baz" => { 1 => :bar } }
248
- ]
249
- }],
250
- "retry" => true,
251
- "jid" => "efb140489485999d32b5504c",
252
- "created_at" => Time.parse("2001-01-01 10:00:00UTC").to_f,
253
- "enqueued_at" => Time.parse("2001-01-01 10:00:00UTC").to_f
254
- }
255
- end
256
-
257
- it "creates a transaction for the ActionMailer class" do
258
- perform_job
259
-
260
- transaction_hash = transaction.to_h
261
- expect(transaction_hash).to include(
262
- "id" => kind_of(String),
263
- "action" => "MailerClass#mailer_method",
264
- "error" => nil,
265
- "namespace" => namespace,
266
- "metadata" => {
267
- "queue" => "default"
268
- },
269
- "sample_data" => {
270
- "environment" => {},
271
- "params" => [
272
- "foo",
273
- {
274
- "foo" => "Foo",
275
- "bar" => "Bar",
276
- "baz" => { "1" => "bar" }
277
- }
278
- ],
279
- "tags" => {}
280
- }
281
- )
282
- end
283
- end
284
-
285
- context "with parameter filtering" do
286
- before do
287
- Appsignal.config = project_fixture_config("production")
288
- Appsignal.config[:filter_parameters] = ["foo"]
289
- end
290
-
291
- it "filters selected arguments" do
292
- perform_job
293
-
294
- transaction_hash = transaction.to_h
295
- expect(transaction_hash["sample_data"]).to include(
296
- "params" => [
297
- "foo",
298
- {
299
- "foo" => "[FILTERED]",
300
- "bar" => "Bar",
301
- "baz" => { "1" => "bar" }
302
- }
303
- ]
304
- )
305
- end
306
- end
307
-
308
- context "when Sidekiq job payload is missing the 'wrapped' value" do
309
- let(:item) do
310
- {
311
- "class" => "ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper",
312
- "queue" => "default",
313
- "args" => [first_argument],
314
- "retry" => true,
315
- "jid" => "efb140489485999d32b5504c",
316
- "created_at" => Time.parse("2001-01-01 10:00:00UTC").to_f,
317
- "enqueued_at" => Time.parse("2001-01-01 10:00:00UTC").to_f
318
- }
319
- end
320
- before { perform_job }
321
-
322
- context "when the first argument is not a Hash object" do
323
- let(:first_argument) { "foo" }
324
-
325
- include_examples "unknown job action name"
326
- end
327
-
328
- context "when the first argument is a Hash object not containing a job payload" do
329
- let(:first_argument) { { "foo" => "bar" } }
330
-
331
- include_examples "unknown job action name"
332
-
333
- context "when the argument contains an invalid job_class value" do
334
- let(:first_argument) { { "job_class" => :foo } }
335
-
336
- include_examples "unknown job action name"
337
- end
338
- end
339
-
340
- context "when the first argument is a Hash object containing a job payload" do
341
- let(:first_argument) do
342
- {
343
- "job_class" => "ActiveMailerTestJob",
344
- "job_id" => "23e79d48-6966-40d0-b2d4-f7938463a263",
345
- "queue_name" => "default",
346
- "arguments" => [
347
- "foo", { "foo" => "Foo", "bar" => "Bar", "baz" => { 1 => :bar } }
348
- ]
349
- }
350
- end
351
-
352
- it "sets the action name to the job class in the first argument" do
353
- transaction_hash = transaction.to_h
354
- expect(transaction_hash).to include(
355
- "action" => "ActiveMailerTestJob#perform"
356
- )
357
- end
358
-
359
- it "stores the job metadata on the transaction" do
360
- transaction_hash = transaction.to_h
361
- expect(transaction_hash).to include(
362
- "id" => kind_of(String),
363
- "error" => nil,
364
- "namespace" => namespace,
365
- "metadata" => {
366
- "queue" => "default"
367
- },
368
- "sample_data" => {
369
- "environment" => {},
370
- "params" => [
371
- "foo",
372
- {
373
- "foo" => "Foo",
374
- "bar" => "Bar",
375
- "baz" => { "1" => "bar" }
376
- }
377
- ],
378
- "tags" => {}
379
- }
380
- )
381
- end
382
-
383
- it "does not log a debug message" do
384
- expect(log_contents(log)).to_not contains_log(
385
- :debug, "Unable to determine an action name from Sidekiq payload"
386
- )
387
- end
388
- end
185
+ expect(transaction_hash["action"]).to eq("Sidekiq::Extensions::DelayedClass#perform")
186
+ expect(transaction_hash["sample_data"]).to include("params" => [])
187
+ expect(log_contents(log)).to contains_log(:warn, "Unable to load YAML")
389
188
  end
390
189
  end
391
190
  end
392
191
 
393
- shared_examples "unknown job action name" do
394
- it "sets the action name to unknown" do
395
- transaction_hash = transaction.to_h
396
- expect(transaction_hash).to include("action" => "unknown")
192
+ context "when using the Sidekiq ActiveRecord instance delayed extension" do
193
+ let(:item) do
194
+ {
195
+ "jid" => jid,
196
+ "class" => "Sidekiq::Extensions::DelayedModel",
197
+ "queue" => "default",
198
+ "args" => [
199
+ "---\n- !ruby/object:DelayedTestClass {}\n- :foo_method\n- - :bar: :baz\n"
200
+ ],
201
+ "retry" => true,
202
+ "created_at" => Time.parse("2001-01-01 10:00:00UTC").to_f,
203
+ "enqueued_at" => Time.parse("2001-01-01 10:00:00UTC").to_f,
204
+ "extra" => "data"
205
+ }
397
206
  end
398
207
 
399
- it "stores no sample data" do
208
+ it "uses the delayed class and method name for the action" do
209
+ perform_job
210
+
400
211
  transaction_hash = transaction.to_h
401
- expect(transaction_hash).to include(
402
- "sample_data" => {
403
- "environment" => {},
404
- "params" => [],
405
- "tags" => {}
406
- }
212
+ expect(transaction_hash["action"]).to eq("DelayedTestClass#foo_method")
213
+ expect(transaction_hash["sample_data"]).to include(
214
+ "params" => ["bar" => "baz"]
407
215
  )
408
216
  end
409
217
 
410
- it "logs a debug message" do
411
- expect(log_contents(log)).to contains_log(
412
- :debug, "Unable to determine an action name from Sidekiq payload: #{item}"
413
- )
218
+ context "when job arguments is a malformed YAML object", :with_yaml_parse_error => true do
219
+ before { item["args"] = [] }
220
+
221
+ it "logs a warning and uses the default argument" do
222
+ perform_job
223
+
224
+ transaction_hash = transaction.to_h
225
+ expect(transaction_hash["action"]).to eq("Sidekiq::Extensions::DelayedModel#perform")
226
+ expect(transaction_hash["sample_data"]).to include("params" => [])
227
+ expect(log_contents(log)).to contains_log(:warn, "Unable to load YAML")
228
+ end
414
229
  end
415
230
  end
416
231
 
@@ -429,7 +244,7 @@ describe Appsignal::Hooks::SidekiqPlugin, :with_yaml_parse_error => false do
429
244
 
430
245
  transaction_hash = transaction.to_h
431
246
  expect(transaction_hash).to include(
432
- "id" => kind_of(String),
247
+ "id" => jid,
433
248
  "action" => "TestClass#perform",
434
249
  "error" => {
435
250
  "name" => "ExampleException",
@@ -452,8 +267,6 @@ describe Appsignal::Hooks::SidekiqPlugin, :with_yaml_parse_error => false do
452
267
  )
453
268
  expect_transaction_to_have_sidekiq_event(transaction_hash)
454
269
  end
455
-
456
- include_examples "sidekiq metadata"
457
270
  end
458
271
 
459
272
  context "without an error" do
@@ -465,7 +278,7 @@ describe Appsignal::Hooks::SidekiqPlugin, :with_yaml_parse_error => false do
465
278
 
466
279
  transaction_hash = transaction.to_h
467
280
  expect(transaction_hash).to include(
468
- "id" => kind_of(String),
281
+ "id" => jid,
469
282
  "action" => "TestClass#perform",
470
283
  "error" => nil,
471
284
  "metadata" => {
@@ -487,8 +300,6 @@ describe Appsignal::Hooks::SidekiqPlugin, :with_yaml_parse_error => false do
487
300
  )
488
301
  expect_transaction_to_have_sidekiq_event(transaction_hash)
489
302
  end
490
-
491
- include_examples "sidekiq metadata"
492
303
  end
493
304
 
494
305
  def perform_job
@@ -516,247 +327,184 @@ describe Appsignal::Hooks::SidekiqPlugin, :with_yaml_parse_error => false do
516
327
  end
517
328
  end
518
329
 
519
- describe Appsignal::Hooks::SidekiqHook do
520
- describe "#dependencies_present?" do
521
- subject { described_class.new.dependencies_present? }
522
-
523
- context "when Sidekiq constant is found" do
524
- before { Object.const_set("Sidekiq", 1) }
525
- after { Object.send(:remove_const, "Sidekiq") }
526
-
527
- it { is_expected.to be_truthy }
330
+ if DependencyHelper.active_job_present?
331
+ require "active_job"
332
+ require "action_mailer"
333
+ require "sidekiq/testing"
334
+
335
+ describe "Sidekiq ActiveJob integration" do
336
+ let(:namespace) { Appsignal::Transaction::BACKGROUND_JOB }
337
+ let(:time) { Time.parse("2001-01-01 10:00:00UTC") }
338
+ let(:log) { StringIO.new }
339
+ let(:given_args) do
340
+ [
341
+ "foo",
342
+ {
343
+ :foo => "Foo",
344
+ "bar" => "Bar",
345
+ "baz" => { "1" => "foo" }
346
+ }
347
+ ]
528
348
  end
529
-
530
- context "when Sidekiq constant is not found" do
531
- before { Object.send(:remove_const, "Sidekiq") if defined?(Sidekiq) }
532
-
533
- it { is_expected.to be_falsy }
349
+ let(:expected_args) do
350
+ [
351
+ "foo",
352
+ {
353
+ "_aj_symbol_keys" => ["foo"],
354
+ "foo" => "Foo",
355
+ "bar" => "Bar",
356
+ "baz" => {
357
+ "_aj_symbol_keys" => [],
358
+ "1" => "foo"
359
+ }
360
+ }
361
+ ]
534
362
  end
535
- end
536
-
537
- describe "#install" do
538
- before do
539
- Appsignal.config = project_fixture_config
540
- class Sidekiq
541
- def self.middlewares
542
- @middlewares ||= Set.new
543
- end
544
-
545
- def self.configure_server
546
- yield self
547
- end
548
-
549
- def self.server_middleware
550
- yield middlewares
363
+ let(:expected_tags) do
364
+ {}.tap do |hash|
365
+ hash["active_job_id"] = kind_of(String)
366
+ if DependencyHelper.rails_version >= Gem::Version.new("5.0.0")
367
+ hash["provider_job_id"] = kind_of(String)
551
368
  end
552
369
  end
553
370
  end
554
- after { Object.send(:remove_const, "Sidekiq") }
555
-
556
- it "adds the AppSignal SidekiqPlugin to the Sidekiq middleware chain" do
557
- described_class.new.install
558
-
559
- expect(Sidekiq.middlewares).to include(Appsignal::Hooks::SidekiqPlugin)
560
- end
561
- end
562
- end
563
-
564
- describe Appsignal::Hooks::SidekiqProbe do
565
- describe "#call" do
566
- let(:probe) { described_class.new }
567
- let(:redis_hostname) { "localhost" }
568
- let(:expected_default_tags) { { :hostname => "localhost" } }
569
371
  before do
570
- Appsignal.config = project_fixture_config
571
- class Sidekiq
572
- def self.redis_info
573
- {
574
- "connected_clients" => 2,
575
- "used_memory" => 1024,
576
- "used_memory_rss" => 512
577
- }
578
- end
579
-
580
- def self.redis
581
- yield Client.new
582
- end
583
-
584
- class Client
585
- def connection
586
- { :host => "localhost" }
587
- end
588
- end
589
-
590
- class Stats
591
- class << self
592
- attr_reader :calls
593
-
594
- def count_call
595
- @calls ||= -1
596
- @calls += 1
597
- end
598
- end
599
-
600
- def workers_size
601
- # First method called, so count it towards a call
602
- self.class.count_call
603
- 24
604
- end
605
-
606
- def processes_size
607
- 25
608
- end
609
-
610
- # Return two different values for two separate calls.
611
- # This allows us to test the delta of the value send as a gauge.
612
- def processed
613
- [10, 15][self.class.calls]
614
- end
615
-
616
- # Return two different values for two separate calls.
617
- # This allows us to test the delta of the value send as a gauge.
618
- def failed
619
- [10, 13][self.class.calls]
620
- end
372
+ start_agent
373
+ Appsignal.logger = test_logger(log)
374
+ ActiveJob::Base.queue_adapter = :sidekiq
621
375
 
622
- def retry_size
623
- 12
624
- end
376
+ class ActiveJobSidekiqTestJob < ActiveJob::Base
377
+ self.queue_adapter = :sidekiq
625
378
 
626
- # Return two different values for two separate calls.
627
- # This allows us to test the delta of the value send as a gauge.
628
- def dead_size
629
- [10, 12][self.class.calls]
630
- end
631
-
632
- def scheduled_size
633
- 14
634
- end
635
-
636
- def enqueued
637
- 15
638
- end
379
+ def perform(*_args)
639
380
  end
381
+ end
640
382
 
641
- class Queue
642
- Queue = Struct.new(:name, :size, :latency)
383
+ class ActiveJobSidekiqErrorTestJob < ActiveJob::Base
384
+ self.queue_adapter = :sidekiq
643
385
 
644
- def self.all
645
- [
646
- Queue.new("default", 10, 12),
647
- Queue.new("critical", 1, 2)
648
- ]
649
- end
386
+ def perform(*_args)
387
+ raise "uh oh"
650
388
  end
651
389
  end
652
- end
653
- after { Object.send(:remove_const, "Sidekiq") }
654
-
655
- describe ".dependencies_present?" do
656
- before do
657
- class Redis; end
658
- Redis.const_set(:VERSION, version)
390
+ # Manually add the AppSignal middleware for the Testing environment.
391
+ # It doesn't use configured middlewares by default looks like.
392
+ # We test somewhere else if the middleware is installed properly.
393
+ Sidekiq::Testing.server_middleware do |chain|
394
+ chain.add Appsignal::Hooks::SidekiqPlugin
659
395
  end
660
- after { Object.send(:remove_const, "Redis") }
661
-
662
- context "when Redis version is < 3.3.5" do
663
- let(:version) { "3.3.4" }
664
-
665
- it "does not start probe" do
666
- expect(described_class.dependencies_present?).to be_falsy
396
+ end
397
+ around do |example|
398
+ keep_transactions do
399
+ Sidekiq::Testing.fake! do
400
+ example.run
667
401
  end
668
402
  end
403
+ end
404
+ after do
405
+ Object.send(:remove_const, :ActiveJobSidekiqTestJob)
406
+ Object.send(:remove_const, :ActiveJobSidekiqErrorTestJob)
407
+ end
669
408
 
670
- context "when Redis version is >= 3.3.5" do
671
- let(:version) { "3.3.5" }
409
+ it "reports the transaction from the ActiveJob integration" do
410
+ perform_job(ActiveJobSidekiqTestJob, given_args)
672
411
 
673
- it "does not start probe" do
674
- expect(described_class.dependencies_present?).to be_truthy
675
- end
676
- end
412
+ transaction = last_transaction
413
+ transaction_hash = transaction.to_h
414
+ expect(transaction_hash).to include(
415
+ "action" => "ActiveJobSidekiqTestJob#perform",
416
+ "error" => nil,
417
+ "namespace" => namespace,
418
+ "metadata" => hash_including(
419
+ "queue" => "default"
420
+ ),
421
+ "sample_data" => hash_including(
422
+ "environment" => {},
423
+ "params" => [expected_args],
424
+ "tags" => expected_tags.merge("queue" => "default")
425
+ )
426
+ )
427
+ expect(transaction.request.env).to eq(:queue_start => time.to_f)
428
+ events = transaction_hash["events"]
429
+ .sort_by { |e| e["start"] }
430
+ .map { |event| event["name"] }
431
+ expect(events)
432
+ .to eq(["perform_job.sidekiq", "perform_start.active_job", "perform.active_job"])
677
433
  end
678
434
 
679
- it "loads Sidekiq::API" do
680
- expect(defined?(Sidekiq::API)).to be_falsy
681
- probe
682
- expect(defined?(Sidekiq::API)).to be_truthy
683
- end
435
+ context "with error" do
436
+ it "reports the error on the transaction from the ActiveRecord integration" do
437
+ expect do
438
+ perform_job(ActiveJobSidekiqErrorTestJob, given_args)
439
+ end.to raise_error(RuntimeError, "uh oh")
684
440
 
685
- it "logs config on initialize" do
686
- log = capture_logs { probe }
687
- expect(log).to contains_log(:debug, "Initializing Sidekiq probe\n")
441
+ transaction = last_transaction
442
+ transaction_hash = transaction.to_h
443
+ expect(transaction_hash).to include(
444
+ "action" => "ActiveJobSidekiqErrorTestJob#perform",
445
+ "error" => {
446
+ "name" => "RuntimeError",
447
+ "message" => "uh oh",
448
+ "backtrace" => kind_of(String)
449
+ },
450
+ "namespace" => namespace,
451
+ "metadata" => hash_including(
452
+ "queue" => "default"
453
+ ),
454
+ "sample_data" => hash_including(
455
+ "environment" => {},
456
+ "params" => [expected_args],
457
+ "tags" => expected_tags.merge("queue" => "default")
458
+ )
459
+ )
460
+ expect(transaction.request.env).to eq(:queue_start => time.to_f)
461
+ events = transaction_hash["events"]
462
+ .sort_by { |e| e["start"] }
463
+ .map { |event| event["name"] }
464
+ expect(events)
465
+ .to eq(["perform_job.sidekiq", "perform_start.active_job", "perform.active_job"])
466
+ end
688
467
  end
689
468
 
690
- it "logs used hostname on call once" do
691
- log = capture_logs { probe.call }
692
- expect(log).to contains_log(
693
- :debug,
694
- %(Sidekiq probe: Using Redis server hostname "localhost" as hostname)
695
- )
696
- log = capture_logs { probe.call }
697
- # Match more logs with incompelete message
698
- expect(log).to_not contains_log(:debug, %(Sidekiq probe: ))
699
- end
700
-
701
- it "collects custom metrics" do
702
- expect_gauge("worker_count", 24).twice
703
- expect_gauge("process_count", 25).twice
704
- expect_gauge("connection_count", 2).twice
705
- expect_gauge("memory_usage", 1024).twice
706
- expect_gauge("memory_usage_rss", 512).twice
707
- expect_gauge("job_count", 5, :status => :processed) # Gauge delta
708
- expect_gauge("job_count", 3, :status => :failed) # Gauge delta
709
- expect_gauge("job_count", 12, :status => :retry_queue).twice
710
- expect_gauge("job_count", 2, :status => :died) # Gauge delta
711
- expect_gauge("job_count", 14, :status => :scheduled).twice
712
- expect_gauge("job_count", 15, :status => :enqueued).twice
713
- expect_gauge("queue_length", 10, :queue => "default").twice
714
- expect_gauge("queue_latency", 12_000, :queue => "default").twice
715
- expect_gauge("queue_length", 1, :queue => "critical").twice
716
- expect_gauge("queue_latency", 2_000, :queue => "critical").twice
717
- # Call probe twice so we can calculate the delta for some gauge values
718
- probe.call
719
- probe.call
720
- end
721
-
722
- context "when `redis_info` is not defined" do
469
+ context "with ActionMailer" do
470
+ include ActionMailerHelpers
471
+
723
472
  before do
724
- allow(Sidekiq).to receive(:respond_to?).with(:redis_info).and_return(false)
473
+ class ActionMailerSidekiqTestJob < ActionMailer::Base
474
+ def welcome(*args)
475
+ end
476
+ end
725
477
  end
726
478
 
727
- it "does not collect redis metrics" do
728
- expect_gauge("connection_count", 2).never
729
- expect_gauge("memory_usage", 1024).never
730
- expect_gauge("memory_usage_rss", 512).never
731
- probe.call
479
+ it "reports ActionMailer data on the transaction" do
480
+ perform_mailer(ActionMailerSidekiqTestJob, :welcome, given_args)
481
+
482
+ transaction = last_transaction
483
+ transaction_hash = transaction.to_h
484
+ expect(transaction_hash).to include(
485
+ "action" => "ActionMailerSidekiqTestJob#welcome",
486
+ "sample_data" => hash_including(
487
+ "params" => ["ActionMailerSidekiqTestJob", "welcome", "deliver_now"] + expected_args
488
+ )
489
+ )
732
490
  end
733
491
  end
734
492
 
735
- context "when hostname is configured for probe" do
736
- let(:redis_hostname) { "my_redis_server" }
737
- let(:probe) { described_class.new(:hostname => redis_hostname) }
738
-
739
- it "uses the redis hostname for the hostname tag" do
740
- allow(Appsignal).to receive(:set_gauge).and_call_original
741
- log = capture_logs { probe }
742
- expect(log).to contains_log(
743
- :debug,
744
- %(Initializing Sidekiq probe with config: {:hostname=>"#{redis_hostname}"})
745
- )
746
- log = capture_logs { probe.call }
747
- expect(log).to contains_log(
748
- :debug,
749
- "Sidekiq probe: Using hostname config option #{redis_hostname.inspect} as hostname"
750
- )
751
- expect(Appsignal).to have_received(:set_gauge)
752
- .with(anything, anything, :hostname => redis_hostname).at_least(:once)
493
+ def perform_sidekiq
494
+ Timecop.freeze(time) do
495
+ yield
496
+ # Combined with Sidekiq::Testing.fake! and drain_all we get a
497
+ # enqueue_at in the job data.
498
+ Sidekiq::Worker.drain_all
753
499
  end
754
500
  end
755
501
 
756
- def expect_gauge(key, value, tags = {})
757
- expect(Appsignal).to receive(:set_gauge)
758
- .with("sidekiq_#{key}", value, expected_default_tags.merge(tags))
759
- .and_call_original
502
+ def perform_job(job_class, args)
503
+ perform_sidekiq { job_class.perform_later(args) }
504
+ end
505
+
506
+ def perform_mailer(mailer, method, args = nil)
507
+ perform_sidekiq { perform_action_mailer(mailer, method, args) }
760
508
  end
761
509
  end
762
510
  end