appsignal 2.10.8-java → 2.11.0.beta.1-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 (64) 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 -0
  5. data/build_matrix.yml +13 -7
  6. data/ext/agent.yml +19 -19
  7. data/ext/appsignal_extension.c +10 -1
  8. data/ext/base.rb +11 -2
  9. data/gemfiles/padrino.gemfile +2 -2
  10. data/gemfiles/rails-4.2.gemfile +9 -2
  11. data/gemfiles/rails-5.0.gemfile +1 -0
  12. data/gemfiles/rails-5.1.gemfile +1 -0
  13. data/gemfiles/rails-5.2.gemfile +1 -0
  14. data/gemfiles/rails-6.0.gemfile +1 -0
  15. data/gemfiles/resque-1.gemfile +7 -0
  16. data/gemfiles/{resque.gemfile → resque-2.gemfile} +1 -1
  17. data/lib/appsignal.rb +21 -1
  18. data/lib/appsignal/capistrano.rb +2 -0
  19. data/lib/appsignal/config.rb +6 -2
  20. data/lib/appsignal/environment.rb +126 -0
  21. data/lib/appsignal/extension/jruby.rb +10 -0
  22. data/lib/appsignal/hooks.rb +2 -0
  23. data/lib/appsignal/hooks/active_job.rb +89 -0
  24. data/lib/appsignal/hooks/net_http.rb +2 -0
  25. data/lib/appsignal/hooks/puma.rb +2 -58
  26. data/lib/appsignal/hooks/redis.rb +2 -0
  27. data/lib/appsignal/hooks/resque.rb +60 -0
  28. data/lib/appsignal/hooks/sequel.rb +2 -0
  29. data/lib/appsignal/hooks/sidekiq.rb +18 -191
  30. data/lib/appsignal/integrations/object.rb +4 -0
  31. data/lib/appsignal/integrations/que.rb +1 -1
  32. data/lib/appsignal/integrations/resque.rb +9 -12
  33. data/lib/appsignal/integrations/resque_active_job.rb +9 -24
  34. data/lib/appsignal/probes/puma.rb +61 -0
  35. data/lib/appsignal/probes/sidekiq.rb +102 -0
  36. data/lib/appsignal/rack/js_exception_catcher.rb +5 -2
  37. data/lib/appsignal/transaction.rb +32 -7
  38. data/lib/appsignal/utils/deprecation_message.rb +5 -1
  39. data/lib/appsignal/version.rb +1 -1
  40. data/lib/puma/plugin/appsignal.rb +2 -1
  41. data/spec/lib/appsignal/cli/diagnose_spec.rb +2 -1
  42. data/spec/lib/appsignal/config_spec.rb +6 -1
  43. data/spec/lib/appsignal/environment_spec.rb +167 -0
  44. data/spec/lib/appsignal/hooks/activejob_spec.rb +458 -0
  45. data/spec/lib/appsignal/hooks/puma_spec.rb +2 -181
  46. data/spec/lib/appsignal/hooks/resque_spec.rb +185 -0
  47. data/spec/lib/appsignal/hooks/sidekiq_spec.rb +292 -546
  48. data/spec/lib/appsignal/integrations/padrino_spec.rb +1 -1
  49. data/spec/lib/appsignal/integrations/que_spec.rb +25 -6
  50. data/spec/lib/appsignal/integrations/resque_active_job_spec.rb +20 -137
  51. data/spec/lib/appsignal/integrations/resque_spec.rb +20 -85
  52. data/spec/lib/appsignal/probes/puma_spec.rb +180 -0
  53. data/spec/lib/appsignal/probes/sidekiq_spec.rb +204 -0
  54. data/spec/lib/appsignal/rack/js_exception_catcher_spec.rb +9 -4
  55. data/spec/lib/appsignal/transaction_spec.rb +35 -20
  56. data/spec/lib/appsignal_spec.rb +22 -0
  57. data/spec/lib/puma/appsignal_spec.rb +1 -1
  58. data/spec/support/helpers/action_mailer_helpers.rb +25 -0
  59. data/spec/support/helpers/dependency_helper.rb +12 -0
  60. data/spec/support/helpers/env_helpers.rb +1 -1
  61. data/spec/support/helpers/environment_metdata_helper.rb +16 -0
  62. data/spec/support/helpers/transaction_helpers.rb +6 -0
  63. data/spec/support/stubs/sidekiq/api.rb +2 -2
  64. metadata +25 -5
@@ -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
 
@@ -48,369 +99,132 @@ describe Appsignal::Hooks::SidekiqPlugin, :with_yaml_parse_error => false do
48
99
  expect(log_contents(log)).to_not contains_log(:warn, "Unable to load YAML")
49
100
  end
50
101
 
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
102
+ describe "internal Sidekiq job values" do
103
+ it "does not save internal Sidekiq values as metadata on transaction" do
104
+ perform_job
55
105
 
56
- transaction_hash = transaction.to_h
57
- expect(transaction_hash["metadata"].keys)
58
- .to_not include(*Appsignal::Hooks::SidekiqPlugin::JOB_KEYS)
59
- end
106
+ transaction_hash = transaction.to_h
107
+ expect(transaction_hash["metadata"].keys)
108
+ .to_not include(*Appsignal::Hooks::SidekiqPlugin::EXCLUDED_JOB_KEYS)
60
109
  end
110
+ end
61
111
 
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
112
+ context "with parameter filtering" do
113
+ before do
114
+ Appsignal.config = project_fixture_config("production")
115
+ Appsignal.config[:filter_parameters] = ["foo"]
83
116
  end
84
117
 
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
118
+ it "filters selected arguments" do
119
+ perform_job
93
120
 
94
- transaction_hash = transaction.to_h
95
- expect(transaction_hash["sample_data"]).to include(
96
- "params" => expected_args << "[encrypted data]"
97
- )
98
- end
121
+ transaction_hash = transaction.to_h
122
+ expect(transaction_hash["sample_data"]).to include(
123
+ "params" => [
124
+ "foo",
125
+ {
126
+ "foo" => "[FILTERED]",
127
+ "bar" => "Bar",
128
+ "baz" => { "1" => "foo" }
129
+ }
130
+ ]
131
+ )
99
132
  end
133
+ end
100
134
 
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
135
+ context "with encrypted arguments" do
136
+ before do
137
+ item["encrypt"] = true
138
+ item["args"] << "super secret value" # Last argument will be replaced
139
139
  end
140
140
 
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
141
+ it "replaces the last argument (the secret bag) with an [encrypted data] string" do
142
+ perform_job
159
143
 
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
144
+ transaction_hash = transaction.to_h
145
+ expect(transaction_hash["sample_data"]).to include(
146
+ "params" => expected_args << "[encrypted data]"
147
+ )
148
+ end
149
+ end
166
150
 
167
- context "when job arguments is a malformed YAML object", :with_yaml_parse_error => true do
168
- before { item["args"] = [] }
151
+ context "when using the Sidekiq delayed extension" do
152
+ let(:item) do
153
+ {
154
+ "jid" => "efb140489485999d32b5504c",
155
+ "class" => "Sidekiq::Extensions::DelayedClass",
156
+ "queue" => "default",
157
+ "args" => [
158
+ "---\n- !ruby/class 'DelayedTestClass'\n- :foo_method\n- - :bar: baz\n"
159
+ ],
160
+ "retry" => true,
161
+ "created_at" => Time.parse("2001-01-01 10:00:00UTC").to_f,
162
+ "enqueued_at" => Time.parse("2001-01-01 10:00:00UTC").to_f,
163
+ "extra" => "data"
164
+ }
165
+ end
169
166
 
170
- it "logs a warning and uses the default argument" do
171
- perform_job
167
+ it "uses the delayed class and method name for the action" do
168
+ perform_job
172
169
 
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
170
+ transaction_hash = transaction.to_h
171
+ expect(transaction_hash["action"]).to eq("DelayedTestClass.foo_method")
172
+ expect(transaction_hash["sample_data"]).to include(
173
+ "params" => ["bar" => "baz"]
174
+ )
179
175
  end
180
176
 
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
177
+ context "when job arguments is a malformed YAML object", :with_yaml_parse_error => true do
178
+ before { item["args"] = [] }
201
179
 
202
- it "creates a transaction with events" do
180
+ it "logs a warning and uses the default argument" do
203
181
  perform_job
204
182
 
205
183
  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
184
+ expect(transaction_hash["action"]).to eq("Sidekiq::Extensions::DelayedClass#perform")
185
+ expect(transaction_hash["sample_data"]).to include("params" => [])
186
+ expect(log_contents(log)).to contains_log(:warn, "Unable to load YAML")
389
187
  end
390
188
  end
391
189
  end
392
190
 
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")
191
+ context "when using the Sidekiq ActiveRecord instance delayed extension" do
192
+ let(:item) do
193
+ {
194
+ "jid" => "efb140489485999d32b5504c",
195
+ "class" => "Sidekiq::Extensions::DelayedModel",
196
+ "queue" => "default",
197
+ "args" => [
198
+ "---\n- !ruby/object:DelayedTestClass {}\n- :foo_method\n- - :bar: :baz\n"
199
+ ],
200
+ "retry" => true,
201
+ "created_at" => Time.parse("2001-01-01 10:00:00UTC").to_f,
202
+ "enqueued_at" => Time.parse("2001-01-01 10:00:00UTC").to_f,
203
+ "extra" => "data"
204
+ }
397
205
  end
398
206
 
399
- it "stores no sample data" do
207
+ it "uses the delayed class and method name for the action" do
208
+ perform_job
209
+
400
210
  transaction_hash = transaction.to_h
401
- expect(transaction_hash).to include(
402
- "sample_data" => {
403
- "environment" => {},
404
- "params" => [],
405
- "tags" => {}
406
- }
211
+ expect(transaction_hash["action"]).to eq("DelayedTestClass#foo_method")
212
+ expect(transaction_hash["sample_data"]).to include(
213
+ "params" => ["bar" => "baz"]
407
214
  )
408
215
  end
409
216
 
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
- )
217
+ context "when job arguments is a malformed YAML object", :with_yaml_parse_error => true do
218
+ before { item["args"] = [] }
219
+
220
+ it "logs a warning and uses the default argument" do
221
+ perform_job
222
+
223
+ transaction_hash = transaction.to_h
224
+ expect(transaction_hash["action"]).to eq("Sidekiq::Extensions::DelayedModel#perform")
225
+ expect(transaction_hash["sample_data"]).to include("params" => [])
226
+ expect(log_contents(log)).to contains_log(:warn, "Unable to load YAML")
227
+ end
414
228
  end
415
229
  end
416
230
 
@@ -452,8 +266,6 @@ describe Appsignal::Hooks::SidekiqPlugin, :with_yaml_parse_error => false do
452
266
  )
453
267
  expect_transaction_to_have_sidekiq_event(transaction_hash)
454
268
  end
455
-
456
- include_examples "sidekiq metadata"
457
269
  end
458
270
 
459
271
  context "without an error" do
@@ -487,8 +299,6 @@ describe Appsignal::Hooks::SidekiqPlugin, :with_yaml_parse_error => false do
487
299
  )
488
300
  expect_transaction_to_have_sidekiq_event(transaction_hash)
489
301
  end
490
-
491
- include_examples "sidekiq metadata"
492
302
  end
493
303
 
494
304
  def perform_job
@@ -516,247 +326,183 @@ describe Appsignal::Hooks::SidekiqPlugin, :with_yaml_parse_error => false do
516
326
  end
517
327
  end
518
328
 
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 }
329
+ if DependencyHelper.active_job_present?
330
+ require "active_job"
331
+ require "action_mailer"
332
+ require "sidekiq/testing"
333
+
334
+ describe "Sidekiq ActiveJob integration" do
335
+ let(:namespace) { Appsignal::Transaction::BACKGROUND_JOB }
336
+ let(:time) { Time.parse("2001-01-01 10:00:00UTC") }
337
+ let(:log) { StringIO.new }
338
+ let(:given_args) do
339
+ [
340
+ "foo",
341
+ {
342
+ :foo => "Foo",
343
+ "bar" => "Bar",
344
+ "baz" => { "1" => "foo" }
345
+ }
346
+ ]
528
347
  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 }
348
+ let(:expected_args) do
349
+ [
350
+ "foo",
351
+ {
352
+ "_aj_symbol_keys" => ["foo"],
353
+ "foo" => "Foo",
354
+ "bar" => "Bar",
355
+ "baz" => {
356
+ "_aj_symbol_keys" => [],
357
+ "1" => "foo"
358
+ }
359
+ }
360
+ ]
534
361
  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
362
+ let(:expected_tags) do
363
+ {}.tap do |hash|
364
+ if DependencyHelper.rails_version >= Gem::Version.new("5.0.0")
365
+ hash["provider_job_id"] = kind_of(String)
551
366
  end
552
367
  end
553
368
  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
369
  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
370
+ start_agent
371
+ Appsignal.logger = test_logger(log)
372
+ ActiveJob::Base.queue_adapter = :sidekiq
621
373
 
622
- def retry_size
623
- 12
624
- end
374
+ class ActiveJobSidekiqTestJob < ActiveJob::Base
375
+ self.queue_adapter = :sidekiq
625
376
 
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
377
+ def perform(*_args)
639
378
  end
379
+ end
640
380
 
641
- class Queue
642
- Queue = Struct.new(:name, :size, :latency)
381
+ class ActiveJobSidekiqErrorTestJob < ActiveJob::Base
382
+ self.queue_adapter = :sidekiq
643
383
 
644
- def self.all
645
- [
646
- Queue.new("default", 10, 12),
647
- Queue.new("critical", 1, 2)
648
- ]
649
- end
384
+ def perform(*_args)
385
+ raise "uh oh"
650
386
  end
651
387
  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)
388
+ # Manually add the AppSignal middleware for the Testing environment.
389
+ # It doesn't use configured middlewares by default looks like.
390
+ # We test somewhere else if the middleware is installed properly.
391
+ Sidekiq::Testing.server_middleware do |chain|
392
+ chain.add Appsignal::Hooks::SidekiqPlugin
659
393
  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
394
+ end
395
+ around do |example|
396
+ keep_transactions do
397
+ Sidekiq::Testing.fake! do
398
+ example.run
667
399
  end
668
400
  end
401
+ end
402
+ after do
403
+ Object.send(:remove_const, :ActiveJobSidekiqTestJob)
404
+ Object.send(:remove_const, :ActiveJobSidekiqErrorTestJob)
405
+ end
669
406
 
670
- context "when Redis version is >= 3.3.5" do
671
- let(:version) { "3.3.5" }
407
+ it "reports the transaction from the ActiveJob integration" do
408
+ perform_job(ActiveJobSidekiqTestJob, given_args)
672
409
 
673
- it "does not start probe" do
674
- expect(described_class.dependencies_present?).to be_truthy
675
- end
676
- end
410
+ transaction = last_transaction
411
+ transaction_hash = transaction.to_h
412
+ expect(transaction_hash).to include(
413
+ "action" => "ActiveJobSidekiqTestJob#perform",
414
+ "error" => nil,
415
+ "namespace" => namespace,
416
+ "metadata" => hash_including(
417
+ "queue" => "default"
418
+ ),
419
+ "sample_data" => hash_including(
420
+ "environment" => {},
421
+ "params" => [expected_args],
422
+ "tags" => expected_tags.merge("queue" => "default")
423
+ )
424
+ )
425
+ expect(transaction.request.env).to eq(:queue_start => time.to_f)
426
+ events = transaction_hash["events"]
427
+ .sort_by { |e| e["start"] }
428
+ .map { |event| event["name"] }
429
+ expect(events)
430
+ .to eq(["perform_job.sidekiq", "perform_start.active_job", "perform.active_job"])
677
431
  end
678
432
 
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
433
+ context "with error" do
434
+ it "reports the error on the transaction from the ActiveRecord integration" do
435
+ expect do
436
+ perform_job(ActiveJobSidekiqErrorTestJob, given_args)
437
+ end.to raise_error(RuntimeError, "uh oh")
684
438
 
685
- it "logs config on initialize" do
686
- log = capture_logs { probe }
687
- expect(log).to contains_log(:debug, "Initializing Sidekiq probe\n")
439
+ transaction = last_transaction
440
+ transaction_hash = transaction.to_h
441
+ expect(transaction_hash).to include(
442
+ "action" => "ActiveJobSidekiqErrorTestJob#perform",
443
+ "error" => {
444
+ "name" => "RuntimeError",
445
+ "message" => "uh oh",
446
+ "backtrace" => kind_of(String)
447
+ },
448
+ "namespace" => namespace,
449
+ "metadata" => hash_including(
450
+ "queue" => "default"
451
+ ),
452
+ "sample_data" => hash_including(
453
+ "environment" => {},
454
+ "params" => [expected_args],
455
+ "tags" => expected_tags.merge("queue" => "default")
456
+ )
457
+ )
458
+ expect(transaction.request.env).to eq(:queue_start => time.to_f)
459
+ events = transaction_hash["events"]
460
+ .sort_by { |e| e["start"] }
461
+ .map { |event| event["name"] }
462
+ expect(events)
463
+ .to eq(["perform_job.sidekiq", "perform_start.active_job", "perform.active_job"])
464
+ end
688
465
  end
689
466
 
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
467
+ context "with ActionMailer" do
468
+ include ActionMailerHelpers
469
+
723
470
  before do
724
- allow(Sidekiq).to receive(:respond_to?).with(:redis_info).and_return(false)
471
+ class ActionMailerSidekiqTestJob < ActionMailer::Base
472
+ def welcome(*args)
473
+ end
474
+ end
725
475
  end
726
476
 
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
477
+ it "reports ActionMailer data on the transaction" do
478
+ perform_mailer(ActionMailerSidekiqTestJob, :welcome, given_args)
479
+
480
+ transaction = last_transaction
481
+ transaction_hash = transaction.to_h
482
+ expect(transaction_hash).to include(
483
+ "action" => "ActionMailerSidekiqTestJob#welcome",
484
+ "sample_data" => hash_including(
485
+ "params" => ["ActionMailerSidekiqTestJob", "welcome", "deliver_now"] + expected_args
486
+ )
487
+ )
732
488
  end
733
489
  end
734
490
 
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)
491
+ def perform_sidekiq
492
+ Timecop.freeze(time) do
493
+ yield
494
+ # Combined with Sidekiq::Testing.fake! and drain_all we get a
495
+ # enqueue_at in the job data.
496
+ Sidekiq::Worker.drain_all
753
497
  end
754
498
  end
755
499
 
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
500
+ def perform_job(job_class, args)
501
+ perform_sidekiq { job_class.perform_later(args) }
502
+ end
503
+
504
+ def perform_mailer(mailer, method, args = nil)
505
+ perform_sidekiq { perform_action_mailer(mailer, method, args) }
760
506
  end
761
507
  end
762
508
  end