appsignal 2.9.16 → 2.9.18
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +15 -0
- data/ext/agent.yml +19 -19
- data/lib/appsignal/cli/install.rb +34 -10
- data/lib/appsignal/config.rb +5 -2
- data/lib/appsignal/hooks/puma.rb +13 -18
- data/lib/appsignal/utils/rails_helper.rb +4 -0
- data/lib/appsignal/version.rb +1 -1
- data/lib/puma/plugin/appsignal.rb +26 -0
- data/spec/lib/appsignal/cli/install_spec.rb +51 -7
- data/spec/lib/appsignal/config_spec.rb +3 -0
- data/spec/lib/appsignal/hooks/puma_spec.rb +43 -35
- data/spec/lib/appsignal/minutely_spec.rb +11 -48
- data/spec/lib/puma/appsignal_spec.rb +91 -0
- data/spec/support/helpers/wait_for_helper.rb +28 -0
- data/spec/support/mocks/mock_probe.rb +11 -0
- metadata +10 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3d3694d0f2970749ad1d8951a3989c2f6ea77c10f3cb695c3992266962bba09e
|
|
4
|
+
data.tar.gz: b16b94135fc7f014ec2a3b5b6904696d6b262972bb229d908f3d0c7f2f6bb6ff
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: cb9e78c6e46f317ea3092e6045afd21abb7bea8ed3858021fbd4d2ef0d4d743a778becf69a3e2c4dc0dd64a00a353b2793601d22ed5163d99c9dfd779f748b8b
|
|
7
|
+
data.tar.gz: fc32dfdfb9beadac21752e730340306674948458a2cc260d66e5dca646efa4bfe7a6c44e01d62a17844f229a08562d55534651c7744d82a9da8c98a8e23e9869
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 2.9.18 (Beta)
|
|
4
|
+
- Bump agent to v-c348132
|
|
5
|
+
- Improve transmitter logging on timeout
|
|
6
|
+
- Improve queued payloads transmitter. Should prevent payloads being sent
|
|
7
|
+
multiple times.
|
|
8
|
+
- Add transaction debug mode
|
|
9
|
+
- Wrap Option in Mutex in TransactionInProgess
|
|
10
|
+
|
|
11
|
+
## 2.9.17
|
|
12
|
+
- Handle missing file and load errors from `application.rb` in `appsignal
|
|
13
|
+
install` for Rails apps. PR #568
|
|
14
|
+
- Support minutely probes for Puma in clustered mode. PR #570
|
|
15
|
+
See the installation instructions for the Puma plugin:
|
|
16
|
+
https://docs.appsignal.com/ruby/integrations/puma.html
|
|
17
|
+
|
|
3
18
|
## 2.9.16
|
|
4
19
|
- Check set_error arguments for Exceptions. PR #565
|
|
5
20
|
- Bump agent to v-1d8917f - commit 737d6b1b8fc9cd2c0564050bb04246d9267dceb7
|
data/ext/agent.yml
CHANGED
|
@@ -1,70 +1,70 @@
|
|
|
1
1
|
---
|
|
2
|
-
version:
|
|
2
|
+
version: c348132
|
|
3
3
|
mirrors:
|
|
4
4
|
- https://appsignal-agent-releases.global.ssl.fastly.net
|
|
5
5
|
- https://d135dj0rjqvssy.cloudfront.net
|
|
6
6
|
triples:
|
|
7
7
|
x86_64-darwin:
|
|
8
8
|
static:
|
|
9
|
-
checksum:
|
|
9
|
+
checksum: cb287c8e2072fe5b8cf14449bd6892989c392d0c651ce339895ae0302cb69785
|
|
10
10
|
filename: appsignal-x86_64-darwin-all-static.tar.gz
|
|
11
11
|
dynamic:
|
|
12
|
-
checksum:
|
|
12
|
+
checksum: af1ed2e9d29859ffbfc8e6903e4c51764dee94d7b4877ca8d30270b6f133a10f
|
|
13
13
|
filename: appsignal-x86_64-darwin-all-dynamic.tar.gz
|
|
14
14
|
universal-darwin:
|
|
15
15
|
static:
|
|
16
|
-
checksum:
|
|
16
|
+
checksum: cb287c8e2072fe5b8cf14449bd6892989c392d0c651ce339895ae0302cb69785
|
|
17
17
|
filename: appsignal-x86_64-darwin-all-static.tar.gz
|
|
18
18
|
dynamic:
|
|
19
|
-
checksum:
|
|
19
|
+
checksum: af1ed2e9d29859ffbfc8e6903e4c51764dee94d7b4877ca8d30270b6f133a10f
|
|
20
20
|
filename: appsignal-x86_64-darwin-all-dynamic.tar.gz
|
|
21
21
|
i686-linux:
|
|
22
22
|
static:
|
|
23
|
-
checksum:
|
|
23
|
+
checksum: 2c3bcd102592bf38fbdb27e7c70502dccbe54a0dc2739a9d54aaa694fcfb41fb
|
|
24
24
|
filename: appsignal-i686-linux-all-static.tar.gz
|
|
25
25
|
dynamic:
|
|
26
|
-
checksum:
|
|
26
|
+
checksum: 1c037b8370b755d706340e25d3e4b2f4acb279dd03873cc53bcf0a6ec0832653
|
|
27
27
|
filename: appsignal-i686-linux-all-dynamic.tar.gz
|
|
28
28
|
x86-linux:
|
|
29
29
|
static:
|
|
30
|
-
checksum:
|
|
30
|
+
checksum: 2c3bcd102592bf38fbdb27e7c70502dccbe54a0dc2739a9d54aaa694fcfb41fb
|
|
31
31
|
filename: appsignal-i686-linux-all-static.tar.gz
|
|
32
32
|
dynamic:
|
|
33
|
-
checksum:
|
|
33
|
+
checksum: 1c037b8370b755d706340e25d3e4b2f4acb279dd03873cc53bcf0a6ec0832653
|
|
34
34
|
filename: appsignal-i686-linux-all-dynamic.tar.gz
|
|
35
35
|
i686-linux-musl:
|
|
36
36
|
static:
|
|
37
|
-
checksum:
|
|
37
|
+
checksum: 0add9eed4452feda7fc5e1bbd0acdff32c353e4ea0b5d527959df57deb1bdcb2
|
|
38
38
|
filename: appsignal-i686-linux-musl-all-static.tar.gz
|
|
39
39
|
x86-linux-musl:
|
|
40
40
|
static:
|
|
41
|
-
checksum:
|
|
41
|
+
checksum: 0add9eed4452feda7fc5e1bbd0acdff32c353e4ea0b5d527959df57deb1bdcb2
|
|
42
42
|
filename: appsignal-i686-linux-musl-all-static.tar.gz
|
|
43
43
|
x86_64-linux:
|
|
44
44
|
static:
|
|
45
|
-
checksum:
|
|
45
|
+
checksum: d11221c127c00128da16b419c503281407e429c0ea6f5bfe1691640b8e995e4e
|
|
46
46
|
filename: appsignal-x86_64-linux-all-static.tar.gz
|
|
47
47
|
dynamic:
|
|
48
|
-
checksum:
|
|
48
|
+
checksum: 6869ab461fde55487d55805c396d55f36cb881998556f44236035b949939b0af
|
|
49
49
|
filename: appsignal-x86_64-linux-all-dynamic.tar.gz
|
|
50
50
|
x86_64-linux-musl:
|
|
51
51
|
static:
|
|
52
|
-
checksum:
|
|
52
|
+
checksum: 7ce44dc23c578933ca37a79d244bc367fdc2438408c2a61558adb92bcfebb1fa
|
|
53
53
|
filename: appsignal-x86_64-linux-musl-all-static.tar.gz
|
|
54
54
|
dynamic:
|
|
55
|
-
checksum:
|
|
55
|
+
checksum: 78d98f468e3a12cc09baff9e68bc4d9cd3b79f4a3bbe744036bff685415546a4
|
|
56
56
|
filename: appsignal-x86_64-linux-musl-all-dynamic.tar.gz
|
|
57
57
|
x86_64-freebsd:
|
|
58
58
|
static:
|
|
59
|
-
checksum:
|
|
59
|
+
checksum: df5f8b61e6ecca40f349cf5c83d5f37f031850d367793dee90dc56f13974431d
|
|
60
60
|
filename: appsignal-x86_64-freebsd-all-static.tar.gz
|
|
61
61
|
dynamic:
|
|
62
|
-
checksum:
|
|
62
|
+
checksum: 30d0303e97386014640c5b8194b777a5741e08ab5497ba58a7d8229bd4890fc5
|
|
63
63
|
filename: appsignal-x86_64-freebsd-all-dynamic.tar.gz
|
|
64
64
|
amd64-freebsd:
|
|
65
65
|
static:
|
|
66
|
-
checksum:
|
|
66
|
+
checksum: df5f8b61e6ecca40f349cf5c83d5f37f031850d367793dee90dc56f13974431d
|
|
67
67
|
filename: appsignal-x86_64-freebsd-all-static.tar.gz
|
|
68
68
|
dynamic:
|
|
69
|
-
checksum:
|
|
69
|
+
checksum: 30d0303e97386014640c5b8194b777a5741e08ab5497ba58a7d8229bd4890fc5
|
|
70
70
|
filename: appsignal-x86_64-freebsd-all-dynamic.tar.gz
|
|
@@ -75,18 +75,39 @@ module Appsignal
|
|
|
75
75
|
def install_for_rails(config)
|
|
76
76
|
puts "Installing for Ruby on Rails"
|
|
77
77
|
|
|
78
|
-
|
|
78
|
+
name_overwritten = configure_rails_app_name(config)
|
|
79
|
+
configure(config, rails_environments, name_overwritten)
|
|
80
|
+
done_notice
|
|
81
|
+
end
|
|
79
82
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
83
|
+
def configure_rails_app_name(config)
|
|
84
|
+
loaded =
|
|
85
|
+
begin
|
|
86
|
+
load Appsignal::Utils::RailsHelper.application_config_path
|
|
87
|
+
true
|
|
88
|
+
rescue LoadError, StandardError
|
|
89
|
+
false
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
name_overwritten = false
|
|
93
|
+
if loaded
|
|
94
|
+
config[:name] = Appsignal::Utils::RailsHelper.detected_rails_app_name
|
|
95
|
+
puts
|
|
96
|
+
name_overwritten = yes_or_no(
|
|
97
|
+
" Your app's name is: '#{config[:name]}' \n " \
|
|
98
|
+
"Do you want to change how this is displayed in AppSignal? " \
|
|
99
|
+
"(y/n): "
|
|
100
|
+
)
|
|
101
|
+
if name_overwritten
|
|
102
|
+
config[:name] = required_input(" Choose app's display name: ")
|
|
103
|
+
puts
|
|
104
|
+
end
|
|
105
|
+
else
|
|
106
|
+
puts " Unable to automatically detect your Rails app's name."
|
|
107
|
+
config[:name] = required_input(" Choose your app's display name for AppSignal.com: ")
|
|
85
108
|
puts
|
|
86
109
|
end
|
|
87
|
-
|
|
88
|
-
configure(config, rails_environments, name_overwritten)
|
|
89
|
-
done_notice
|
|
110
|
+
name_overwritten
|
|
90
111
|
end
|
|
91
112
|
|
|
92
113
|
def install_for_sinatra(config)
|
|
@@ -227,7 +248,10 @@ module Appsignal
|
|
|
227
248
|
|
|
228
249
|
def installed_frameworks
|
|
229
250
|
[].tap do |out|
|
|
230
|
-
|
|
251
|
+
if framework_available?("rails") &&
|
|
252
|
+
File.exist?(Appsignal::Utils::RailsHelper.application_config_path)
|
|
253
|
+
out << :rails
|
|
254
|
+
end
|
|
231
255
|
out << :sinatra if framework_available? "sinatra"
|
|
232
256
|
out << :padrino if framework_available? "padrino"
|
|
233
257
|
out << :grape if framework_available? "grape"
|
data/lib/appsignal/config.rb
CHANGED
|
@@ -39,7 +39,8 @@ module Appsignal
|
|
|
39
39
|
:enable_minutely_probes => true,
|
|
40
40
|
:ca_file_path => File.expand_path(File.join("../../../resources/cacert.pem"), __FILE__),
|
|
41
41
|
:dns_servers => [],
|
|
42
|
-
:files_world_accessible => true
|
|
42
|
+
:files_world_accessible => true,
|
|
43
|
+
:transaction_debug_mode => false
|
|
43
44
|
}.freeze
|
|
44
45
|
|
|
45
46
|
ENV_TO_KEY_MAPPING = {
|
|
@@ -75,6 +76,7 @@ module Appsignal
|
|
|
75
76
|
"APPSIGNAL_DNS_SERVERS" => :dns_servers,
|
|
76
77
|
"APPSIGNAL_FILES_WORLD_ACCESSIBLE" => :files_world_accessible,
|
|
77
78
|
"APPSIGNAL_REQUEST_HEADERS" => :request_headers,
|
|
79
|
+
"APPSIGNAL_TRANSACTION_DEBUG_MODE" => :transaction_debug_mode,
|
|
78
80
|
"APP_REVISION" => :revision
|
|
79
81
|
}.freeze
|
|
80
82
|
|
|
@@ -219,6 +221,7 @@ module Appsignal
|
|
|
219
221
|
ENV["_APPSIGNAL_CA_FILE_PATH"] = config_hash[:ca_file_path].to_s
|
|
220
222
|
ENV["_APPSIGNAL_DNS_SERVERS"] = config_hash[:dns_servers].join(",")
|
|
221
223
|
ENV["_APPSIGNAL_FILES_WORLD_ACCESSIBLE"] = config_hash[:files_world_accessible].to_s
|
|
224
|
+
ENV["_APPSIGNAL_TRANSACTION_DEBUG_MODE"] = config_hash[:transaction_debug_mode].to_s
|
|
222
225
|
ENV["_APP_REVISION"] = config_hash[:revision].to_s
|
|
223
226
|
end
|
|
224
227
|
|
|
@@ -324,7 +327,7 @@ module Appsignal
|
|
|
324
327
|
APPSIGNAL_ENABLE_ALLOCATION_TRACKING APPSIGNAL_ENABLE_GC_INSTRUMENTATION
|
|
325
328
|
APPSIGNAL_RUNNING_IN_CONTAINER APPSIGNAL_ENABLE_HOST_METRICS
|
|
326
329
|
APPSIGNAL_SEND_PARAMS APPSIGNAL_ENABLE_MINUTELY_PROBES
|
|
327
|
-
APPSIGNAL_FILES_WORLD_ACCESSIBLE].each do |var|
|
|
330
|
+
APPSIGNAL_FILES_WORLD_ACCESSIBLE APPSIGNAL_TRANSACTION_DEBUG_MODE].each do |var|
|
|
328
331
|
env_var = ENV[var]
|
|
329
332
|
next unless env_var
|
|
330
333
|
config[ENV_TO_KEY_MAPPING[var]] = env_var.casecmp("true").zero?
|
data/lib/appsignal/hooks/puma.rb
CHANGED
|
@@ -11,27 +11,22 @@ module Appsignal
|
|
|
11
11
|
end
|
|
12
12
|
|
|
13
13
|
def install
|
|
14
|
-
if ::Puma.respond_to?(:stats)
|
|
14
|
+
if ::Puma.respond_to?(:stats) && !defined?(APPSIGNAL_PUMA_PLUGIN_LOADED)
|
|
15
|
+
# Only install the minutely probe if a user isn't using our Puma
|
|
16
|
+
# plugin, which lives in `lib/puma/appsignal.rb`. This plugin defines
|
|
17
|
+
# the {APPSIGNAL_PUMA_PLUGIN_LOADED} constant.
|
|
18
|
+
#
|
|
19
|
+
# We prefer people use the AppSignal Puma plugin. This fallback is
|
|
20
|
+
# only there when users relied on our *magic* integration.
|
|
21
|
+
#
|
|
22
|
+
# Using the Puma plugin, the minutely probe thread will still run in
|
|
23
|
+
# Puma workers, for other non-Puma probes, but the Puma probe only
|
|
24
|
+
# runs in the Puma main process.
|
|
25
|
+
# For more information:
|
|
26
|
+
# https://docs.appsignal.com/ruby/integrations/puma.html
|
|
15
27
|
Appsignal::Minutely.probes.register :puma, PumaProbe
|
|
16
28
|
end
|
|
17
29
|
|
|
18
|
-
if ::Puma.respond_to?(:cli_config) && ::Puma.cli_config
|
|
19
|
-
::Puma.cli_config.options[:before_fork] ||= []
|
|
20
|
-
::Puma.cli_config.options[:before_fork] << proc do |_id|
|
|
21
|
-
Appsignal::Minutely.start
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
::Puma.cli_config.options[:before_worker_boot] ||= []
|
|
25
|
-
::Puma.cli_config.options[:before_worker_boot] << proc do |_id|
|
|
26
|
-
Appsignal.forked
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
::Puma.cli_config.options[:before_worker_shutdown] ||= []
|
|
30
|
-
::Puma.cli_config.options[:before_worker_shutdown] << proc do |_id|
|
|
31
|
-
Appsignal.stop("puma before_worker_shutdown")
|
|
32
|
-
end
|
|
33
|
-
end
|
|
34
|
-
|
|
35
30
|
return unless defined?(::Puma::Cluster)
|
|
36
31
|
# For clustered mode with multiple workers
|
|
37
32
|
::Puma::Cluster.class_eval do
|
data/lib/appsignal/version.rb
CHANGED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
APPSIGNAL_PUMA_PLUGIN_LOADED = true
|
|
2
|
+
|
|
3
|
+
# AppSignal Puma plugin
|
|
4
|
+
#
|
|
5
|
+
# This plugin ensures the minutely probe thread is started with the Puma
|
|
6
|
+
# minutely probe in the Puma master process.
|
|
7
|
+
#
|
|
8
|
+
# The constant {APPSIGNAL_PUMA_PLUGIN_LOADED} is here to mark the Plugin as
|
|
9
|
+
# loaded by the rest of the AppSignal gem. This ensures that the Puma minutely
|
|
10
|
+
# probe is not also started in every Puma workers, which was the old behavior.
|
|
11
|
+
# See {Appsignal::Hooks::PumaHook#install} for more information.
|
|
12
|
+
#
|
|
13
|
+
# For even more information:
|
|
14
|
+
# https://docs.appsignal.com/ruby/integrations/puma.html
|
|
15
|
+
Puma::Plugin.create do
|
|
16
|
+
def start(launcher = nil)
|
|
17
|
+
launcher.events.on_booted do
|
|
18
|
+
require "appsignal"
|
|
19
|
+
if ::Puma.respond_to?(:stats)
|
|
20
|
+
Appsignal::Minutely.probes.register :puma, Appsignal::Hooks::PumaProbe
|
|
21
|
+
end
|
|
22
|
+
Appsignal.start
|
|
23
|
+
Appsignal.start_logger
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -16,6 +16,10 @@ describe Appsignal::CLI::Install do
|
|
|
16
16
|
allow(described_class).to receive(:press_any_key)
|
|
17
17
|
allow(Appsignal::Demo).to receive(:transmit).and_return(true)
|
|
18
18
|
end
|
|
19
|
+
after do
|
|
20
|
+
FileUtils.rm_rf(tmp_dir)
|
|
21
|
+
FileUtils.mkdir_p(tmp_dir)
|
|
22
|
+
end
|
|
19
23
|
around do |example|
|
|
20
24
|
original_stdin = $stdin
|
|
21
25
|
$stdin = StringIO.new
|
|
@@ -157,16 +161,10 @@ describe Appsignal::CLI::Install do
|
|
|
157
161
|
shared_examples "capistrano install" do
|
|
158
162
|
let(:capfile) { File.join(tmp_dir, "Capfile") }
|
|
159
163
|
before do
|
|
160
|
-
FileUtils.mkdir_p(tmp_dir)
|
|
161
|
-
|
|
162
164
|
enter_app_name "foo"
|
|
163
165
|
add_cli_input "n"
|
|
164
166
|
choose_environment_config
|
|
165
167
|
end
|
|
166
|
-
after do
|
|
167
|
-
FileUtils.rm_rf(tmp_dir)
|
|
168
|
-
FileUtils.mkdir_p(tmp_dir)
|
|
169
|
-
end
|
|
170
168
|
|
|
171
169
|
context "without Capfile" do
|
|
172
170
|
it "does nothing" do
|
|
@@ -260,7 +258,6 @@ describe Appsignal::CLI::Install do
|
|
|
260
258
|
FileUtils.touch(File.join(environments_dir, "development.rb"))
|
|
261
259
|
FileUtils.touch(File.join(environments_dir, "staging.rb"))
|
|
262
260
|
FileUtils.touch(File.join(environments_dir, "production.rb"))
|
|
263
|
-
enter_app_name app_name
|
|
264
261
|
end
|
|
265
262
|
|
|
266
263
|
describe "environments" do
|
|
@@ -410,6 +407,53 @@ describe Appsignal::CLI::Install do
|
|
|
410
407
|
end
|
|
411
408
|
end
|
|
412
409
|
end
|
|
410
|
+
|
|
411
|
+
context "when there is no Rails application.rb file" do
|
|
412
|
+
before do
|
|
413
|
+
# Do not detect it as another framework for testing
|
|
414
|
+
allow(described_class).to receive(:framework_available?).and_call_original
|
|
415
|
+
allow(described_class).to receive(:framework_available?).with("sinatra").and_return(false)
|
|
416
|
+
|
|
417
|
+
File.delete(File.join(config_dir, "application.rb"))
|
|
418
|
+
expect(File.exist?(File.join(config_dir, "application.rb"))).to eql(false)
|
|
419
|
+
end
|
|
420
|
+
|
|
421
|
+
it "fails the installation" do
|
|
422
|
+
run
|
|
423
|
+
|
|
424
|
+
expect(output).to include("We could not detect which framework you are using.")
|
|
425
|
+
expect(output).to_not include("Installing for Ruby on Rails")
|
|
426
|
+
expect(output).to include_complete_install
|
|
427
|
+
|
|
428
|
+
expect(File.exist?(config_file_path)).to be(false)
|
|
429
|
+
end
|
|
430
|
+
end
|
|
431
|
+
|
|
432
|
+
context "when failed to load the Rails application.rb file" do
|
|
433
|
+
before do
|
|
434
|
+
File.open(File.join(config_dir, "application.rb"), "w") do |file|
|
|
435
|
+
file.write("I am invalid code")
|
|
436
|
+
end
|
|
437
|
+
end
|
|
438
|
+
|
|
439
|
+
it "prompts the user to fill in an app name" do
|
|
440
|
+
enter_app_name app_name
|
|
441
|
+
choose_config_file
|
|
442
|
+
run
|
|
443
|
+
|
|
444
|
+
expect(output).to include("Installing for Ruby on Rails")
|
|
445
|
+
expect(output).to include("Unable to automatically detect your Rails app's name.")
|
|
446
|
+
expect(output).to include("Choose your app's display name for AppSignal.com:")
|
|
447
|
+
expect(output).to include_file_config
|
|
448
|
+
expect(output).to include_complete_install
|
|
449
|
+
|
|
450
|
+
expect(config_file).to configure_app_name(app_name)
|
|
451
|
+
expect(config_file).to configure_push_api_key(push_api_key)
|
|
452
|
+
expect(config_file).to configure_environment("development")
|
|
453
|
+
expect(config_file).to configure_environment("staging")
|
|
454
|
+
expect(config_file).to configure_environment("production")
|
|
455
|
+
end
|
|
456
|
+
end
|
|
413
457
|
end
|
|
414
458
|
end
|
|
415
459
|
|
|
@@ -134,6 +134,7 @@ describe Appsignal::Config do
|
|
|
134
134
|
:ca_file_path => File.join(resources_dir, "cacert.pem"),
|
|
135
135
|
:dns_servers => [],
|
|
136
136
|
:files_world_accessible => true,
|
|
137
|
+
:transaction_debug_mode => false,
|
|
137
138
|
:revision => "v2.5.1",
|
|
138
139
|
:request_headers => []
|
|
139
140
|
)
|
|
@@ -473,6 +474,7 @@ describe Appsignal::Config do
|
|
|
473
474
|
config[:filter_parameters] = %w[password confirm_password]
|
|
474
475
|
config[:running_in_container] = false
|
|
475
476
|
config[:dns_servers] = ["8.8.8.8", "8.8.4.4"]
|
|
477
|
+
config[:transaction_debug_mode] = true
|
|
476
478
|
config[:revision] = "v2.5.1"
|
|
477
479
|
config.write_to_environment
|
|
478
480
|
end
|
|
@@ -500,6 +502,7 @@ describe Appsignal::Config do
|
|
|
500
502
|
expect(ENV["_APPSIGNAL_CA_FILE_PATH"]).to eq File.join(resources_dir, "cacert.pem")
|
|
501
503
|
expect(ENV["_APPSIGNAL_DNS_SERVERS"]).to eq "8.8.8.8,8.8.4.4"
|
|
502
504
|
expect(ENV["_APPSIGNAL_FILES_WORLD_ACCESSIBLE"]).to eq "true"
|
|
505
|
+
expect(ENV["_APPSIGNAL_TRANSACTION_DEBUG_MODE"]).to eq "true"
|
|
503
506
|
expect(ENV["_APP_REVISION"]).to eq "v2.5.1"
|
|
504
507
|
expect(ENV).to_not have_key("_APPSIGNAL_WORKING_DIR_PATH")
|
|
505
508
|
expect(ENV).to_not have_key("_APPSIGNAL_WORKING_DIRECTORY_PATH")
|
|
@@ -27,6 +27,8 @@ describe Appsignal::Hooks::PumaHook do
|
|
|
27
27
|
end
|
|
28
28
|
|
|
29
29
|
describe "installation" do
|
|
30
|
+
before { Appsignal::Minutely.probes.clear }
|
|
31
|
+
|
|
30
32
|
context "when not clustered mode" do
|
|
31
33
|
it "does not add AppSignal stop behavior Puma::Cluster" do
|
|
32
34
|
expect(defined?(::Puma::Cluster)).to be_falsy
|
|
@@ -34,9 +36,27 @@ describe Appsignal::Hooks::PumaHook do
|
|
|
34
36
|
Appsignal::Hooks::PumaHook.new.install
|
|
35
37
|
end
|
|
36
38
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
39
|
+
context "with APPSIGNAL_PUMA_PLUGIN_LOADED defined" do
|
|
40
|
+
before do
|
|
41
|
+
# Set in lib/puma/appsignal.rb
|
|
42
|
+
APPSIGNAL_PUMA_PLUGIN_LOADED = true
|
|
43
|
+
end
|
|
44
|
+
after { Object.send :remove_const, :APPSIGNAL_PUMA_PLUGIN_LOADED }
|
|
45
|
+
|
|
46
|
+
it "does not add the Puma minutely probe" do
|
|
47
|
+
Appsignal::Hooks::PumaHook.new.install
|
|
48
|
+
expect(Appsignal::Minutely.probes[:puma]).to be_nil
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
context "without APPSIGNAL_PUMA_PLUGIN_LOADED defined" do
|
|
53
|
+
it "adds the Puma minutely probe" do
|
|
54
|
+
expect(defined?(APPSIGNAL_PUMA_PLUGIN_LOADED)).to be_nil
|
|
55
|
+
|
|
56
|
+
Appsignal::Hooks::PumaHook.new.install
|
|
57
|
+
probe = Appsignal::Minutely.probes[:puma]
|
|
58
|
+
expect(probe).to eql(Appsignal::Hooks::PumaProbe)
|
|
59
|
+
end
|
|
40
60
|
end
|
|
41
61
|
end
|
|
42
62
|
|
|
@@ -49,11 +69,11 @@ describe Appsignal::Hooks::PumaHook do
|
|
|
49
69
|
end
|
|
50
70
|
end
|
|
51
71
|
end
|
|
52
|
-
Appsignal::Hooks::PumaHook.new.install
|
|
53
72
|
end
|
|
54
73
|
after { Puma.send(:remove_const, :Cluster) }
|
|
55
74
|
|
|
56
75
|
it "adds behavior to Puma::Cluster.stop_workers" do
|
|
76
|
+
Appsignal::Hooks::PumaHook.new.install
|
|
57
77
|
cluster = Puma::Cluster.new
|
|
58
78
|
|
|
59
79
|
expect(cluster.instance_variable_defined?(:@called)).to be_falsy
|
|
@@ -62,40 +82,28 @@ describe Appsignal::Hooks::PumaHook do
|
|
|
62
82
|
expect(cluster.instance_variable_get(:@called)).to be(true)
|
|
63
83
|
end
|
|
64
84
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
context "with nil hooks" do
|
|
73
|
-
before do
|
|
74
|
-
Puma.cli_config.options.delete(:before_fork)
|
|
75
|
-
Puma.cli_config.options.delete(:before_worker_boot)
|
|
76
|
-
Puma.cli_config.options.delete(:before_worker_shutdown)
|
|
77
|
-
Appsignal::Hooks::PumaHook.new.install
|
|
78
|
-
end
|
|
85
|
+
context "with APPSIGNAL_PUMA_PLUGIN_LOADED defined" do
|
|
86
|
+
before do
|
|
87
|
+
# Set in lib/puma/appsignal.rb
|
|
88
|
+
APPSIGNAL_PUMA_PLUGIN_LOADED = true
|
|
89
|
+
end
|
|
90
|
+
after { Object.send :remove_const, :APPSIGNAL_PUMA_PLUGIN_LOADED }
|
|
79
91
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
end
|
|
92
|
+
it "does not add the Puma minutely probe" do
|
|
93
|
+
Appsignal::Hooks::PumaHook.new.install
|
|
94
|
+
expect(Appsignal::Minutely.probes[:puma]).to be_nil
|
|
95
|
+
end
|
|
96
|
+
end
|
|
86
97
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
Puma.cli_config.options[:before_worker_boot] = []
|
|
91
|
-
Puma.cli_config.options[:before_worker_shutdown] = []
|
|
92
|
-
Appsignal::Hooks::PumaHook.new.install
|
|
93
|
-
end
|
|
98
|
+
context "without APPSIGNAL_PUMA_PLUGIN_LOADED defined" do
|
|
99
|
+
it "adds the Puma minutely probe" do
|
|
100
|
+
expect(defined?(APPSIGNAL_PUMA_PLUGIN_LOADED)).to be_nil
|
|
94
101
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
102
|
+
Appsignal::Hooks::PumaHook.new.install
|
|
103
|
+
probe = Appsignal::Minutely.probes[:puma]
|
|
104
|
+
expect(probe).to eql(Appsignal::Hooks::PumaProbe)
|
|
105
|
+
end
|
|
106
|
+
end
|
|
99
107
|
end
|
|
100
108
|
end
|
|
101
109
|
end
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
describe Appsignal::Minutely do
|
|
2
|
+
include WaitForHelper
|
|
3
|
+
|
|
2
4
|
before { Appsignal::Minutely.probes.clear }
|
|
3
5
|
|
|
4
6
|
it "returns a ProbeCollection" do
|
|
@@ -7,38 +9,26 @@ describe Appsignal::Minutely do
|
|
|
7
9
|
end
|
|
8
10
|
|
|
9
11
|
describe ".start" do
|
|
10
|
-
class
|
|
11
|
-
attr_reader :calls
|
|
12
|
-
|
|
13
|
-
def initialize
|
|
14
|
-
@calls = 0
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
def call
|
|
18
|
-
@calls += 1
|
|
19
|
-
end
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
class ProbeWithoutDependency < Probe
|
|
12
|
+
class ProbeWithoutDependency < MockProbe
|
|
23
13
|
def self.dependencies_present?
|
|
24
14
|
true
|
|
25
15
|
end
|
|
26
16
|
end
|
|
27
17
|
|
|
28
|
-
class ProbeWithMissingDependency <
|
|
18
|
+
class ProbeWithMissingDependency < MockProbe
|
|
29
19
|
def self.dependencies_present?
|
|
30
20
|
false
|
|
31
21
|
end
|
|
32
22
|
end
|
|
33
23
|
|
|
34
|
-
class BrokenProbe <
|
|
24
|
+
class BrokenProbe < MockProbe
|
|
35
25
|
def call
|
|
36
26
|
super
|
|
37
27
|
raise "oh no!"
|
|
38
28
|
end
|
|
39
29
|
end
|
|
40
30
|
|
|
41
|
-
class BrokenProbeOnInitialize <
|
|
31
|
+
class BrokenProbeOnInitialize < MockProbe
|
|
42
32
|
def initialize
|
|
43
33
|
super
|
|
44
34
|
raise "oh no initialize!"
|
|
@@ -60,7 +50,7 @@ describe Appsignal::Minutely do
|
|
|
60
50
|
|
|
61
51
|
context "with an instance of a class" do
|
|
62
52
|
it "calls the probe every <wait_time>" do
|
|
63
|
-
probe =
|
|
53
|
+
probe = MockProbe.new
|
|
64
54
|
Appsignal::Minutely.probes.register :my_probe, probe
|
|
65
55
|
Appsignal::Minutely.start
|
|
66
56
|
|
|
@@ -90,8 +80,8 @@ describe Appsignal::Minutely do
|
|
|
90
80
|
|
|
91
81
|
context "with probe class" do
|
|
92
82
|
it "creates an instance of the class and call that every <wait time>" do
|
|
93
|
-
probe =
|
|
94
|
-
probe_instance =
|
|
83
|
+
probe = MockProbe
|
|
84
|
+
probe_instance = MockProbe.new
|
|
95
85
|
expect(probe).to receive(:new).and_return(probe_instance)
|
|
96
86
|
Appsignal::Minutely.probes.register :my_probe, probe
|
|
97
87
|
Appsignal::Minutely.start
|
|
@@ -163,7 +153,7 @@ describe Appsignal::Minutely do
|
|
|
163
153
|
|
|
164
154
|
context "with a broken probe" do
|
|
165
155
|
it "logs the error and continues calling the probes every <wait_time>" do
|
|
166
|
-
probe =
|
|
156
|
+
probe = MockProbe.new
|
|
167
157
|
broken_probe = BrokenProbe.new
|
|
168
158
|
Appsignal::Minutely.probes.register :my_probe, probe
|
|
169
159
|
Appsignal::Minutely.probes.register :broken_probe, broken_probe
|
|
@@ -183,7 +173,7 @@ describe Appsignal::Minutely do
|
|
|
183
173
|
|
|
184
174
|
it "ensures only one minutely probes thread is active at a time" do
|
|
185
175
|
alive_thread_counter = proc { Thread.list.reject { |t| t.status == "dead" }.length }
|
|
186
|
-
probe =
|
|
176
|
+
probe = MockProbe.new
|
|
187
177
|
Appsignal::Minutely.probes.register :my_probe, probe
|
|
188
178
|
expect do
|
|
189
179
|
Appsignal::Minutely.start
|
|
@@ -349,31 +339,4 @@ describe Appsignal::Minutely do
|
|
|
349
339
|
end
|
|
350
340
|
end
|
|
351
341
|
end
|
|
352
|
-
|
|
353
|
-
# Wait for a condition to be met
|
|
354
|
-
#
|
|
355
|
-
# @example
|
|
356
|
-
# # Perform threaded operation
|
|
357
|
-
# wait_for("enough probe calls") { probe.calls >= 2 }
|
|
358
|
-
# # Assert on result
|
|
359
|
-
#
|
|
360
|
-
# @param name [String] The name of the condition to check. Used in the
|
|
361
|
-
# error when it fails.
|
|
362
|
-
# @yield Assertion to check.
|
|
363
|
-
# @yieldreturn [Boolean] True/False value that indicates if the condition
|
|
364
|
-
# is met.
|
|
365
|
-
# @raise [StandardError] Raises error if the condition is not met after 5
|
|
366
|
-
# seconds, 5_000 tries.
|
|
367
|
-
def wait_for(name)
|
|
368
|
-
max_wait = 5_000
|
|
369
|
-
i = 0
|
|
370
|
-
while i <= max_wait
|
|
371
|
-
break if yield
|
|
372
|
-
i += 1
|
|
373
|
-
sleep 0.001
|
|
374
|
-
end
|
|
375
|
-
|
|
376
|
-
return unless i == max_wait
|
|
377
|
-
raise "Waited 5 seconds for #{name} condition, but was not met."
|
|
378
|
-
end
|
|
379
342
|
end
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
RSpec.describe "Puma plugin" do
|
|
2
|
+
include WaitForHelper
|
|
3
|
+
|
|
4
|
+
class MockPumaLauncher
|
|
5
|
+
def events
|
|
6
|
+
return @events if defined?(@events)
|
|
7
|
+
|
|
8
|
+
@events = MockPumaEvents.new
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
class MockPumaEvents
|
|
13
|
+
def on_booted(&block)
|
|
14
|
+
@on_booted = block if block_given?
|
|
15
|
+
@on_booted if defined?(@on_booted)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
let(:probe) { MockProbe.new }
|
|
20
|
+
let(:launcher) { MockPumaLauncher.new }
|
|
21
|
+
before do
|
|
22
|
+
module Puma
|
|
23
|
+
def self.stats
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
class Plugin
|
|
27
|
+
class << self
|
|
28
|
+
attr_reader :plugin
|
|
29
|
+
|
|
30
|
+
def create(&block)
|
|
31
|
+
@plugin = Class.new(::Puma::Plugin)
|
|
32
|
+
@plugin.class_eval(&block)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
Appsignal::Minutely.probes.clear
|
|
39
|
+
ENV["APPSIGNAL_ENABLE_MINUTELY_PROBES"] = "true"
|
|
40
|
+
Appsignal.config = project_fixture_config
|
|
41
|
+
# Speed up test time
|
|
42
|
+
allow(Appsignal::Minutely).to receive(:initial_wait_time).and_return(0.001)
|
|
43
|
+
allow(Appsignal::Minutely).to receive(:wait_time).and_return(0.001)
|
|
44
|
+
|
|
45
|
+
Appsignal::Minutely.probes.register :my_probe, probe
|
|
46
|
+
load File.expand_path("../lib/puma/plugin/appsignal.rb", APPSIGNAL_SPEC_DIR)
|
|
47
|
+
end
|
|
48
|
+
after do
|
|
49
|
+
Appsignal.config = nil
|
|
50
|
+
Object.send :remove_const, :Puma
|
|
51
|
+
Object.send :remove_const, :APPSIGNAL_PUMA_PLUGIN_LOADED
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
it "registers the PumaProbe" do
|
|
55
|
+
expect(Appsignal::Minutely.probes[:my_probe]).to eql(probe)
|
|
56
|
+
expect(Appsignal::Minutely.probes[:puma]).to be_nil
|
|
57
|
+
plugin = Puma::Plugin.plugin.new
|
|
58
|
+
expect(launcher.events.on_booted).to be_nil
|
|
59
|
+
|
|
60
|
+
plugin.start(launcher)
|
|
61
|
+
expect(Appsignal::Minutely.probes[:puma]).to be_nil
|
|
62
|
+
expect(launcher.events.on_booted).to_not be_nil
|
|
63
|
+
|
|
64
|
+
launcher.events.on_booted.call
|
|
65
|
+
expect(Appsignal::Minutely.probes[:puma]).to eql(Appsignal::Hooks::PumaProbe)
|
|
66
|
+
|
|
67
|
+
# Minutely probes started and called
|
|
68
|
+
wait_for("enough probe calls") { probe.calls >= 2 }
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
context "without Puma.stats" do
|
|
72
|
+
before { Puma.singleton_class.send(:remove_method, :stats) }
|
|
73
|
+
|
|
74
|
+
it "does not register the PumaProbe" do
|
|
75
|
+
expect(Appsignal::Minutely.probes[:my_probe]).to eql(probe)
|
|
76
|
+
expect(Appsignal::Minutely.probes[:puma]).to be_nil
|
|
77
|
+
plugin = Puma::Plugin.plugin.new
|
|
78
|
+
expect(launcher.events.on_booted).to be_nil
|
|
79
|
+
|
|
80
|
+
plugin.start(launcher)
|
|
81
|
+
expect(Appsignal::Minutely.probes[:puma]).to be_nil
|
|
82
|
+
expect(launcher.events.on_booted).to_not be_nil
|
|
83
|
+
|
|
84
|
+
launcher.events.on_booted.call
|
|
85
|
+
expect(Appsignal::Minutely.probes[:puma]).to be_nil
|
|
86
|
+
|
|
87
|
+
# Minutely probes started and called
|
|
88
|
+
wait_for("enough probe calls") { probe.calls >= 2 }
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
module WaitForHelper
|
|
2
|
+
# Wait for a condition to be met
|
|
3
|
+
#
|
|
4
|
+
# @example
|
|
5
|
+
# # Perform threaded operation
|
|
6
|
+
# wait_for("enough probe calls") { probe.calls >= 2 }
|
|
7
|
+
# # Assert on result
|
|
8
|
+
#
|
|
9
|
+
# @param name [String] The name of the condition to check. Used in the
|
|
10
|
+
# error when it fails.
|
|
11
|
+
# @yield Assertion to check.
|
|
12
|
+
# @yieldreturn [Boolean] True/False value that indicates if the condition
|
|
13
|
+
# is met.
|
|
14
|
+
# @raise [StandardError] Raises error if the condition is not met after 5
|
|
15
|
+
# seconds, 5_000 tries.
|
|
16
|
+
def wait_for(name)
|
|
17
|
+
max_wait = 5_000
|
|
18
|
+
i = 0
|
|
19
|
+
while i <= max_wait
|
|
20
|
+
break if yield
|
|
21
|
+
i += 1
|
|
22
|
+
sleep 0.001
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
return unless i == max_wait
|
|
26
|
+
raise "Waited 5 seconds for #{name} condition, but was not met."
|
|
27
|
+
end
|
|
28
|
+
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: appsignal
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.9.
|
|
4
|
+
version: 2.9.18
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Robert Beekman
|
|
@@ -10,7 +10,7 @@ authors:
|
|
|
10
10
|
autorequire:
|
|
11
11
|
bindir: bin
|
|
12
12
|
cert_chain: []
|
|
13
|
-
date: 2019-
|
|
13
|
+
date: 2019-11-19 00:00:00.000000000 Z
|
|
14
14
|
dependencies:
|
|
15
15
|
- !ruby/object:Gem::Dependency
|
|
16
16
|
name: rack
|
|
@@ -255,6 +255,7 @@ files:
|
|
|
255
255
|
- lib/appsignal/utils/query_params_sanitizer.rb
|
|
256
256
|
- lib/appsignal/utils/rails_helper.rb
|
|
257
257
|
- lib/appsignal/version.rb
|
|
258
|
+
- lib/puma/plugin/appsignal.rb
|
|
258
259
|
- lib/sequel/extensions/appsignal_integration.rb
|
|
259
260
|
- resources/appsignal.yml.erb
|
|
260
261
|
- resources/cacert.pem
|
|
@@ -328,6 +329,7 @@ files:
|
|
|
328
329
|
- spec/lib/appsignal/utils/json_spec.rb
|
|
329
330
|
- spec/lib/appsignal/utils/query_params_sanitizer_spec.rb
|
|
330
331
|
- spec/lib/appsignal_spec.rb
|
|
332
|
+
- spec/lib/puma/appsignal_spec.rb
|
|
331
333
|
- spec/spec_helper.rb
|
|
332
334
|
- spec/support/fixtures/generated_config.yml
|
|
333
335
|
- spec/support/fixtures/projects/valid/config/application.rb
|
|
@@ -350,9 +352,11 @@ files:
|
|
|
350
352
|
- spec/support/helpers/system_helpers.rb
|
|
351
353
|
- spec/support/helpers/time_helpers.rb
|
|
352
354
|
- spec/support/helpers/transaction_helpers.rb
|
|
355
|
+
- spec/support/helpers/wait_for_helper.rb
|
|
353
356
|
- spec/support/matchers/contains_log.rb
|
|
354
357
|
- spec/support/mocks/fake_gc_profiler.rb
|
|
355
358
|
- spec/support/mocks/mock_extension.rb
|
|
359
|
+
- spec/support/mocks/mock_probe.rb
|
|
356
360
|
- spec/support/rails/my_app.rb
|
|
357
361
|
- spec/support/shared_examples/instrument.rb
|
|
358
362
|
- spec/support/stubs/delayed_job.rb
|
|
@@ -384,7 +388,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
384
388
|
- !ruby/object:Gem::Version
|
|
385
389
|
version: '0'
|
|
386
390
|
requirements: []
|
|
387
|
-
rubygems_version: 3.0.
|
|
391
|
+
rubygems_version: 3.0.6
|
|
388
392
|
signing_key:
|
|
389
393
|
specification_version: 4
|
|
390
394
|
summary: Logs performance and exception data from your app to appsignal.com
|
|
@@ -459,6 +463,7 @@ test_files:
|
|
|
459
463
|
- spec/lib/appsignal/utils/json_spec.rb
|
|
460
464
|
- spec/lib/appsignal/utils/query_params_sanitizer_spec.rb
|
|
461
465
|
- spec/lib/appsignal_spec.rb
|
|
466
|
+
- spec/lib/puma/appsignal_spec.rb
|
|
462
467
|
- spec/spec_helper.rb
|
|
463
468
|
- spec/support/fixtures/generated_config.yml
|
|
464
469
|
- spec/support/fixtures/projects/valid/config/application.rb
|
|
@@ -481,9 +486,11 @@ test_files:
|
|
|
481
486
|
- spec/support/helpers/system_helpers.rb
|
|
482
487
|
- spec/support/helpers/time_helpers.rb
|
|
483
488
|
- spec/support/helpers/transaction_helpers.rb
|
|
489
|
+
- spec/support/helpers/wait_for_helper.rb
|
|
484
490
|
- spec/support/matchers/contains_log.rb
|
|
485
491
|
- spec/support/mocks/fake_gc_profiler.rb
|
|
486
492
|
- spec/support/mocks/mock_extension.rb
|
|
493
|
+
- spec/support/mocks/mock_probe.rb
|
|
487
494
|
- spec/support/rails/my_app.rb
|
|
488
495
|
- spec/support/shared_examples/instrument.rb
|
|
489
496
|
- spec/support/stubs/delayed_job.rb
|