appsignal 2.1.2 → 2.2.0.beta.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (114) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +14 -0
  3. data/.rubocop_todo.yml +16 -171
  4. data/.travis.yml +14 -1
  5. data/.yardopts +8 -0
  6. data/CHANGELOG.md +21 -3
  7. data/README.md +20 -2
  8. data/Rakefile +60 -62
  9. data/appsignal.gemspec +24 -23
  10. data/ext/agent.yml +11 -11
  11. data/ext/appsignal_extension.c +43 -12
  12. data/ext/extconf.rb +9 -9
  13. data/gemfiles/padrino.gemfile +1 -1
  14. data/lib/appsignal.rb +403 -26
  15. data/lib/appsignal/auth_check.rb +28 -1
  16. data/lib/appsignal/cli.rb +1 -0
  17. data/lib/appsignal/cli/demo.rb +40 -0
  18. data/lib/appsignal/cli/diagnose.rb +345 -89
  19. data/lib/appsignal/cli/helpers.rb +9 -4
  20. data/lib/appsignal/cli/install.rb +6 -6
  21. data/lib/appsignal/cli/notify_of_deploy.rb +58 -0
  22. data/lib/appsignal/config.rb +40 -38
  23. data/lib/appsignal/demo.rb +20 -0
  24. data/lib/appsignal/event_formatter.rb +4 -0
  25. data/lib/appsignal/event_formatter/action_view/render_formatter.rb +1 -0
  26. data/lib/appsignal/event_formatter/active_record/instantiation_formatter.rb +1 -0
  27. data/lib/appsignal/event_formatter/active_record/sql_formatter.rb +1 -0
  28. data/lib/appsignal/event_formatter/elastic_search/search_formatter.rb +1 -0
  29. data/lib/appsignal/event_formatter/faraday/request_formatter.rb +1 -0
  30. data/lib/appsignal/event_formatter/mongo_ruby_driver/query_formatter.rb +2 -1
  31. data/lib/appsignal/event_formatter/moped/query_formatter.rb +1 -0
  32. data/lib/appsignal/extension.rb +1 -0
  33. data/lib/appsignal/garbage_collection_profiler.rb +20 -19
  34. data/lib/appsignal/hooks.rb +1 -0
  35. data/lib/appsignal/hooks/active_support_notifications.rb +1 -0
  36. data/lib/appsignal/hooks/celluloid.rb +1 -0
  37. data/lib/appsignal/hooks/data_mapper.rb +1 -0
  38. data/lib/appsignal/hooks/delayed_job.rb +1 -0
  39. data/lib/appsignal/hooks/mongo_ruby_driver.rb +1 -0
  40. data/lib/appsignal/hooks/net_http.rb +1 -0
  41. data/lib/appsignal/hooks/passenger.rb +1 -0
  42. data/lib/appsignal/hooks/puma.rb +1 -0
  43. data/lib/appsignal/hooks/rake.rb +1 -0
  44. data/lib/appsignal/hooks/redis.rb +1 -0
  45. data/lib/appsignal/hooks/sequel.rb +1 -0
  46. data/lib/appsignal/hooks/shoryuken.rb +1 -0
  47. data/lib/appsignal/hooks/sidekiq.rb +1 -0
  48. data/lib/appsignal/hooks/unicorn.rb +1 -0
  49. data/lib/appsignal/hooks/webmachine.rb +1 -0
  50. data/lib/appsignal/integrations/capistrano/appsignal.cap +4 -4
  51. data/lib/appsignal/integrations/capistrano/capistrano_2_tasks.rb +2 -0
  52. data/lib/appsignal/integrations/data_mapper.rb +1 -1
  53. data/lib/appsignal/integrations/delayed_job_plugin.rb +1 -0
  54. data/lib/appsignal/integrations/grape.rb +3 -1
  55. data/lib/appsignal/integrations/mongo_ruby_driver.rb +1 -0
  56. data/lib/appsignal/integrations/padrino.rb +36 -21
  57. data/lib/appsignal/integrations/railtie.rb +2 -2
  58. data/lib/appsignal/integrations/resque.rb +1 -0
  59. data/lib/appsignal/integrations/resque_active_job.rb +1 -0
  60. data/lib/appsignal/integrations/webmachine.rb +27 -24
  61. data/lib/appsignal/js_exception_transaction.rb +8 -8
  62. data/lib/appsignal/marker.rb +41 -4
  63. data/lib/appsignal/minutely.rb +1 -0
  64. data/lib/appsignal/rack/generic_instrumentation.rb +3 -2
  65. data/lib/appsignal/rack/js_exception_catcher.rb +55 -15
  66. data/lib/appsignal/rack/rails_instrumentation.rb +2 -1
  67. data/lib/appsignal/rack/sinatra_instrumentation.rb +4 -2
  68. data/lib/appsignal/rack/streaming_listener.rb +4 -2
  69. data/lib/appsignal/system.rb +5 -31
  70. data/lib/appsignal/transaction.rb +71 -6
  71. data/lib/appsignal/transmitter.rb +24 -13
  72. data/lib/appsignal/utils.rb +18 -11
  73. data/lib/appsignal/utils/params_sanitizer.rb +2 -1
  74. data/lib/appsignal/utils/query_params_sanitizer.rb +1 -0
  75. data/lib/appsignal/version.rb +1 -1
  76. data/resources/appsignal.yml.erb +1 -1
  77. data/spec/lib/appsignal/auth_check_spec.rb +64 -22
  78. data/spec/lib/appsignal/cli/diagnose_spec.rb +539 -81
  79. data/spec/lib/appsignal/cli/helpers_spec.rb +74 -2
  80. data/spec/lib/appsignal/cli/install_spec.rb +3 -3
  81. data/spec/lib/appsignal/config_spec.rb +38 -72
  82. data/spec/lib/appsignal/demo_spec.rb +2 -2
  83. data/spec/lib/appsignal/event_formatter_spec.rb +2 -2
  84. data/spec/lib/appsignal/extension_spec.rb +4 -0
  85. data/spec/lib/appsignal/garbage_collection_profiler_spec.rb +18 -21
  86. data/spec/lib/appsignal/hooks/mongo_ruby_driver_spec.rb +1 -1
  87. data/spec/lib/appsignal/hooks/redis_spec.rb +34 -44
  88. data/spec/lib/appsignal/hooks/sidekiq_spec.rb +2 -2
  89. data/spec/lib/appsignal/integrations/grape_spec.rb +6 -6
  90. data/spec/lib/appsignal/integrations/padrino_spec.rb +241 -122
  91. data/spec/lib/appsignal/integrations/railtie_spec.rb +34 -16
  92. data/spec/lib/appsignal/integrations/resque_spec.rb +1 -1
  93. data/spec/lib/appsignal/integrations/sinatra_spec.rb +38 -10
  94. data/spec/lib/appsignal/integrations/webmachine_spec.rb +2 -2
  95. data/spec/lib/appsignal/rack/generic_instrumentation_spec.rb +7 -6
  96. data/spec/lib/appsignal/rack/js_exception_catcher_spec.rb +95 -58
  97. data/spec/lib/appsignal/rack/rails_instrumentation_spec.rb +9 -6
  98. data/spec/lib/appsignal/rack/sinatra_instrumentation_spec.rb +11 -10
  99. data/spec/lib/appsignal/rack/streaming_listener_spec.rb +20 -13
  100. data/spec/lib/appsignal/system_spec.rb +2 -32
  101. data/spec/lib/appsignal/transaction_spec.rb +48 -7
  102. data/spec/lib/appsignal/transmitter_spec.rb +52 -33
  103. data/spec/lib/appsignal/utils/params_sanitizer_spec.rb +3 -1
  104. data/spec/lib/appsignal/utils/query_params_sanitizer_spec.rb +3 -3
  105. data/spec/lib/appsignal/utils_spec.rb +49 -6
  106. data/spec/lib/appsignal_spec.rb +60 -5
  107. data/spec/spec_helper.rb +4 -3
  108. data/spec/support/helpers/api_request_helper.rb +1 -4
  109. data/spec/support/helpers/dependency_helper.rb +4 -0
  110. data/spec/support/helpers/system_helpers.rb +1 -17
  111. data/spec/support/helpers/time_helpers.rb +1 -1
  112. metadata +19 -8
  113. data/spec/lib/appsignal/system/container_spec.rb +0 -67
  114. data/spec/lib/appsignal/utils/gzip_spec.rb +0 -10
@@ -1,5 +1,6 @@
1
1
  module Appsignal
2
2
  module Utils
3
+ # @api private
3
4
  class ParamsSanitizer
4
5
  FILTERED = "[FILTERED]".freeze
5
6
 
@@ -16,7 +17,7 @@ module Appsignal
16
17
  sanitize_hash(value, options)
17
18
  when Array
18
19
  sanitize_array(value, options)
19
- when TrueClass, FalseClass, NilClass, Fixnum, String, Symbol, Float
20
+ when TrueClass, FalseClass, NilClass, Integer, String, Symbol, Float
20
21
  unmodified(value)
21
22
  else
22
23
  inspected(value)
@@ -1,5 +1,6 @@
1
1
  module Appsignal
2
2
  module Utils
3
+ # @api private
3
4
  class QueryParamsSanitizer
4
5
  REPLACEMENT_KEY = "?".freeze
5
6
 
@@ -1,5 +1,5 @@
1
1
  require "yaml"
2
2
 
3
3
  module Appsignal
4
- VERSION = "2.1.2"
4
+ VERSION = "2.2.0.beta.1".freeze
5
5
  end
@@ -10,7 +10,7 @@ default: &defaults
10
10
  # ignore_actions:
11
11
  # - ApplicationController#isup
12
12
 
13
- # See http://docs.appsignal.com/gem-settings/configuration.html for
13
+ # See http://docs.appsignal.com/ruby/configuration/options.html for
14
14
  # all configuration options.
15
15
 
16
16
  # Configuration per environment, leave out an environment or set active
@@ -1,37 +1,79 @@
1
1
  describe Appsignal::AuthCheck do
2
2
  let(:config) { project_fixture_config }
3
3
  let(:auth_check) { Appsignal::AuthCheck.new(config) }
4
+ let(:auth_url) do
5
+ query = {
6
+ :api_key => config[:push_api_key],
7
+ :environment => config.env,
8
+ :gem_version => Appsignal::VERSION,
9
+ :hostname => config[:hostname],
10
+ :name => config[:name]
11
+ }.map { |k, v| "#{k}=#{v}" }.join("&")
4
12
 
5
- describe "#perform_with_result" do
6
- it "should give success message" do
7
- expect(auth_check).to receive(:perform).and_return("200")
8
- expect(auth_check.perform_with_result).to eq ["200", "AppSignal has confirmed authorization!"]
9
- end
13
+ URI(config[:endpoint]).tap do |uri|
14
+ uri.path = "/1/auth"
15
+ uri.query = query
16
+ end.to_s
17
+ end
18
+ let(:stubbed_request) do
19
+ WebMock.stub_request(:post, auth_url).with(:body => "{}")
20
+ end
21
+
22
+ describe "#perform" do
23
+ subject { auth_check.perform }
24
+
25
+ context "when performing a request against the push api" do
26
+ before { stubbed_request.to_return(:status => 200) }
10
27
 
11
- it "should give 401 message" do
12
- expect(auth_check).to receive(:perform).and_return("401")
13
- expect(auth_check.perform_with_result).to eq ["401", "API key not valid with AppSignal..."]
28
+ it "returns status code" do
29
+ is_expected.to eq("200")
30
+ end
14
31
  end
15
32
 
16
- it "should give an error message" do
17
- expect(auth_check).to receive(:perform).and_return("402")
18
- expect(auth_check.perform_with_result).to eq ["402", "Could not confirm authorization: 402"]
33
+ context "when encountering an exception" do
34
+ before { stubbed_request.to_timeout }
35
+
36
+ it "raises an error" do
37
+ expect { subject }.to raise_error(Net::OpenTimeout)
38
+ end
19
39
  end
20
40
  end
21
41
 
22
- context "transmitting" do
23
- before do
24
- @transmitter = double
25
- expect(Appsignal::Transmitter).to receive(:new).with(
26
- "auth",
27
- kind_of(Appsignal::Config)
28
- ).and_return(@transmitter)
42
+ describe "#perform_with_result" do
43
+ subject { auth_check.perform_with_result }
44
+
45
+ context "when successful response" do
46
+ before { stubbed_request.to_return(:status => 200) }
47
+
48
+ it "returns success tuple" do
49
+ is_expected.to eq ["200", "AppSignal has confirmed authorization!"]
50
+ end
51
+ end
52
+
53
+ context "when unauthorized response" do
54
+ before { stubbed_request.to_return(:status => 401) }
55
+
56
+ it "returns unauthorirzed tuple" do
57
+ is_expected.to eq ["401", "API key not valid with AppSignal..."]
58
+ end
29
59
  end
30
60
 
31
- describe "#perform" do
32
- it "should not transmit any extra data" do
33
- expect(@transmitter).to receive(:transmit).with({}).and_return({})
34
- auth_check.perform
61
+ context "when unrecognized response" do
62
+ before { stubbed_request.to_return(:status => 500) }
63
+
64
+ it "returns an error tuple" do
65
+ is_expected.to eq ["500", "Could not confirm authorization: 500"]
66
+ end
67
+ end
68
+
69
+ context "when encountering an exception" do
70
+ before { stubbed_request.to_timeout }
71
+
72
+ it "returns an error tuple" do
73
+ is_expected.to eq [
74
+ nil,
75
+ "Something went wrong while trying to authenticate with AppSignal: execution expired"
76
+ ]
35
77
  end
36
78
  end
37
79
  end
@@ -1,21 +1,53 @@
1
1
  require "appsignal/cli"
2
2
 
3
- describe Appsignal::CLI::Diagnose, :api_stub => true do
3
+ describe Appsignal::CLI::Diagnose, :api_stub => true, :report => true do
4
+ include CLIHelpers
5
+
6
+ class DiagnosticsReportEndpoint
7
+ class << self
8
+ attr_reader :received_report
9
+
10
+ def clear_report!
11
+ @received_report = nil
12
+ end
13
+
14
+ def call(env)
15
+ @received_report = JSON.parse(env["rack.input"].read)["diagnose"]
16
+ [200, {}, [JSON.generate(:token => "my_support_token")]]
17
+ end
18
+ end
19
+ end
20
+
4
21
  describe ".run" do
5
22
  let(:out_stream) { std_stream }
6
23
  let(:output) { out_stream.read }
7
24
  let(:config) { project_fixture_config }
8
25
  let(:cli) { described_class }
9
26
  let(:options) { { :environment => config.env } }
27
+ let(:gem_path) { Bundler::CLI::Common.select_spec("appsignal").full_gem_path.strip }
28
+ let(:received_report) { DiagnosticsReportEndpoint.received_report }
29
+ let(:process_user) { Etc.getpwuid(Process.uid).name }
10
30
  before(:context) { Appsignal.stop }
11
31
  before do
32
+ DiagnosticsReportEndpoint.clear_report!
12
33
  if DependencyHelper.rails_present?
13
34
  allow(Rails).to receive(:root).and_return(Pathname.new(config.root_path))
14
35
  end
15
36
  end
37
+ around do |example|
38
+ original_stdin = $stdin
39
+ $stdin = StringIO.new
40
+ example.run
41
+ $stdin = original_stdin
42
+ end
16
43
  before :api_stub => true do
17
44
  stub_api_request config, "auth"
18
45
  end
46
+ before :report => true do
47
+ send_diagnostics_report
48
+ stub_diagnostics_report_request.to_rack(DiagnosticsReportEndpoint)
49
+ end
50
+ before(:report => false) { dont_send_diagnostics_report }
19
51
  after { Appsignal.config = nil }
20
52
 
21
53
  def run
@@ -23,11 +55,33 @@ describe Appsignal::CLI::Diagnose, :api_stub => true do
23
55
  end
24
56
 
25
57
  def run_within_dir(chdir)
58
+ prepare_input
26
59
  Dir.chdir chdir do
27
60
  capture_stdout(out_stream) { cli.run(options) }
28
61
  end
29
62
  end
30
63
 
64
+ def stub_diagnostics_report_request
65
+ stub_request(:post, "https://appsignal.com/diag").with(
66
+ :query => {
67
+ :api_key => config[:push_api_key],
68
+ :environment => config.env,
69
+ :gem_version => Appsignal::VERSION,
70
+ :hostname => config[:hostname],
71
+ :name => config[:name]
72
+ },
73
+ :headers => { "Content-Type" => "application/json; charset=UTF-8" }
74
+ )
75
+ end
76
+
77
+ def send_diagnostics_report
78
+ set_input "y"
79
+ end
80
+
81
+ def dont_send_diagnostics_report
82
+ set_input "n"
83
+ end
84
+
31
85
  it "outputs header and support text" do
32
86
  run
33
87
  expect(output).to include \
@@ -36,31 +90,87 @@ describe Appsignal::CLI::Diagnose, :api_stub => true do
36
90
  "support@appsignal.com"
37
91
  end
38
92
 
93
+ describe "report" do
94
+ context "when user wants to send report" do
95
+ it "sends report" do
96
+ run
97
+ expect(output).to include "Diagnostics report",
98
+ "Send diagnostics report to AppSignal? (Y/n): ",
99
+ "Your diagnostics report has been sent to AppSignal."
100
+ end
101
+
102
+ it "outputs the support token from the server" do
103
+ run
104
+ expect(output).to include "Your support token: my_support_token"
105
+ end
106
+
107
+ context "when server response is invalid" do
108
+ before do
109
+ stub_diagnostics_report_request
110
+ .to_return(:status => 200, :body => %({ foo: "Invalid json", a: }))
111
+ run
112
+ end
113
+
114
+ it "outputs the server response in full" do
115
+ expect(output).to include "Error: Couldn't decode server response.",
116
+ %({ foo: "Invalid json", a: })
117
+ end
118
+ end
119
+
120
+ context "when server returns an error" do
121
+ before do
122
+ stub_diagnostics_report_request
123
+ .to_return(:status => 500, :body => "report: server error")
124
+ run
125
+ end
126
+
127
+ it "outputs the server response in full" do
128
+ expect(output).to include "report: server error"
129
+ end
130
+ end
131
+ end
132
+
133
+ context "when user doesn't want to send report", :report => false do
134
+ it "does not send report" do
135
+ run
136
+ expect(output).to include "Diagnostics report",
137
+ "Send diagnostics report to AppSignal? (Y/n): ",
138
+ "Not sending diagnostics information to AppSignal."
139
+ end
140
+ end
141
+ end
142
+
39
143
  describe "agent information" do
144
+ before { run }
145
+
40
146
  it "outputs version numbers" do
41
- run
42
- gem_path = Bundler::CLI::Common.select_spec("appsignal").full_gem_path.strip
43
147
  expect(output).to include \
44
148
  "Gem version: #{Appsignal::VERSION}",
45
149
  "Agent version: #{Appsignal::Extension.agent_version}",
46
150
  "Gem install path: #{gem_path}"
47
151
  end
48
152
 
153
+ it "transmits version numbers in report" do
154
+ expect(received_report).to include(
155
+ "library" => {
156
+ "language" => "ruby",
157
+ "package_version" => Appsignal::VERSION,
158
+ "agent_version" => Appsignal::Extension.agent_version,
159
+ "package_install_path" => gem_path,
160
+ "extension_loaded" => true
161
+ }
162
+ )
163
+ end
164
+
49
165
  context "with extension" do
166
+ before { run }
167
+
50
168
  it "outputs extension is loaded" do
51
- run
52
- expect(output).to include "Extension loaded: yes"
169
+ expect(output).to include "Extension loaded: true"
53
170
  end
54
171
 
55
- it "starts the agent in diagnose mode and outputs a log" do
56
- run
57
- expect(output).to include \
58
- "Agent diagnostics:",
59
- "Running agent in diagnose mode",
60
- "Valid config present",
61
- "Logger initialized successfully",
62
- "Lock path is writable",
63
- "Agent diagnose finished"
172
+ it "transmits extension_loaded: true in report" do
173
+ expect(received_report["library"]["extension_loaded"]).to eq(true)
64
174
  end
65
175
  end
66
176
 
@@ -75,26 +185,217 @@ describe Appsignal::CLI::Diagnose, :api_stub => true do
75
185
  after { Appsignal.extension_loaded = true }
76
186
 
77
187
  it "outputs extension is not loaded" do
78
- expect(output).to include "Extension loaded: no"
188
+ expect(output).to include "Extension loaded: false"
189
+ end
190
+
191
+ it "transmits extension_loaded: false in report" do
192
+ expect(received_report["library"]["extension_loaded"]).to eq(false)
193
+ end
194
+ end
195
+ end
196
+
197
+ describe "agent diagnostics" do
198
+ it "starts the agent in diagnose mode and outputs the report" do
199
+ run
200
+ expect(output).to include \
201
+ "Agent diagnostics",
202
+ " Extension config: valid",
203
+ " Agent config: valid",
204
+ " Agent logger: started",
205
+ " Agent lock path: writable"
206
+ end
207
+
208
+ it "adds the agent diagnostics to the report" do
209
+ run
210
+ expect(received_report["agent"]).to eq(
211
+ "extension" => {
212
+ "config" => { "valid" => { "result" => true } }
213
+ },
214
+ "agent" => {
215
+ "boot" => { "started" => { "result" => true } },
216
+ "config" => { "valid" => { "result" => true } },
217
+ "logger" => { "started" => { "result" => true } },
218
+ "lock_path" => { "created" => { "result" => true } }
219
+ }
220
+ )
221
+ end
222
+
223
+ context "when user config has active: false" do
224
+ before do
225
+ # ENV is leading so easiest to set in test to force user config with active: false
226
+ ENV["APPSIGNAL_ACTIVE"] = "false"
227
+ end
228
+
229
+ it "force starts the agent in diagnose mode and outputs a log" do
230
+ run
231
+ expect(output).to include("active: false")
232
+ expect(output).to include \
233
+ "Agent diagnostics",
234
+ " Extension config: valid",
235
+ " Agent started: started",
236
+ " Agent config: valid",
237
+ " Agent logger: started",
238
+ " Agent lock path: writable"
239
+ end
240
+ end
241
+
242
+ context "when the extention returns invalid JSON" do
243
+ before do
244
+ expect(Appsignal::Extension).to receive(:diagnose).and_return("invalid agent\njson")
245
+ run
246
+ end
247
+
248
+ it "prints a JSON parse error and prints the returned value" do
249
+ expect(output).to include \
250
+ "Agent diagnostics",
251
+ " Error while parsing agent diagnostics report:",
252
+ " Output: invalid agent\njson"
253
+ expect(output).to match(/Error: \d+: unexpected token at 'invalid agent\njson'/)
254
+ end
255
+
256
+ it "adds the output to the report" do
257
+ expect(received_report["agent"]["error"])
258
+ .to match(/\d+: unexpected token at 'invalid agent\njson'/)
259
+ expect(received_report["agent"]["output"]).to eq(["invalid agent", "json"])
260
+ end
261
+ end
262
+
263
+ context "when the report contains an error" do
264
+ let(:agent_report) do
265
+ { "error" => "fatal error" }
266
+ end
267
+ before do
268
+ expect(Appsignal::Extension).to receive(:diagnose).and_return(JSON.generate(agent_report))
269
+ run
270
+ end
271
+
272
+ it "prints an error for the entire report" do
273
+ expect(output).to include "Agent diagnostics\n Error: fatal error"
274
+ end
275
+
276
+ it "adds the error to the report" do
277
+ expect(received_report["agent"]).to eq(agent_report)
278
+ end
279
+ end
280
+
281
+ context "when the report is incomplete (agent failed to start)" do
282
+ let(:agent_report) do
283
+ {
284
+ "extension" => {
285
+ "config" => { "valid" => { "result" => false } }
286
+ }
287
+ # missing agent section
288
+ }
289
+ end
290
+ before do
291
+ expect(Appsignal::Extension).to receive(:diagnose).and_return(JSON.generate(agent_report))
292
+ run
293
+ end
294
+
295
+ it "prints the tests, but shows a dash `-` for missed results" do
296
+ expect(output).to include \
297
+ "Agent diagnostics",
298
+ " Extension config: invalid",
299
+ " Agent started: -",
300
+ " Agent config: -",
301
+ " Agent logger: -",
302
+ " Agent lock path: -"
303
+ end
304
+
305
+ it "adds the output to the report" do
306
+ expect(received_report["agent"]).to eq(agent_report)
307
+ end
308
+ end
309
+
310
+ context "when a test contains an error" do
311
+ let(:agent_report) do
312
+ {
313
+ "extension" => {
314
+ "config" => { "valid" => { "result" => true } }
315
+ },
316
+ "agent" => {
317
+ "boot" => {
318
+ "started" => { "result" => false, "error" => "some-error" }
319
+ }
320
+ }
321
+ }
322
+ end
323
+ before do
324
+ expect(Appsignal::Extension).to receive(:diagnose).and_return(JSON.generate(agent_report))
325
+ run
326
+ end
327
+
328
+ it "prints the error and output" do
329
+ expect(output).to include \
330
+ "Agent diagnostics",
331
+ " Extension config: valid",
332
+ " Agent started: not started\n Error: some-error"
333
+ end
334
+
335
+ it "adds the agent report to the diagnostics report" do
336
+ expect(received_report["agent"]).to eq(agent_report)
337
+ end
338
+ end
339
+
340
+ context "when a test contains command output" do
341
+ let(:agent_report) do
342
+ {
343
+ "extension" => {
344
+ "config" => { "valid" => { "result" => true } }
345
+ },
346
+ "agent" => {
347
+ "config" => { "valid" => { "result" => false, "output" => "some output" } }
348
+ }
349
+ }
350
+ end
351
+
352
+ it "prints the command output" do
353
+ expect(Appsignal::Extension).to receive(:diagnose).and_return(JSON.generate(agent_report))
354
+ run
355
+ expect(output).to include \
356
+ "Agent diagnostics",
357
+ " Extension config: valid",
358
+ " Agent config: invalid\n Output: some output"
79
359
  end
80
360
  end
81
361
  end
82
362
 
83
363
  describe "host information" do
364
+ let(:rbconfig) { RbConfig::CONFIG }
365
+ let(:language_version) { "#{rbconfig["ruby_version"]}-p#{rbconfig["PATCHLEVEL"]}" }
366
+
84
367
  it "outputs host information" do
85
368
  run
86
369
  expect(output).to include \
87
370
  "Host information",
88
- "Architecture: #{RbConfig::CONFIG["host_cpu"]}",
89
- "Operating System: #{RbConfig::CONFIG["host_os"]}",
90
- "Ruby version: #{RbConfig::CONFIG["RUBY_VERSION_NAME"]}"
371
+ "Architecture: #{rbconfig["host_cpu"]}",
372
+ "Operating System: #{rbconfig["host_os"]}",
373
+ "Ruby version: #{language_version}"
374
+ end
375
+
376
+ it "transmits host information in report" do
377
+ run
378
+ host_report = received_report["host"]
379
+ host_report.delete("running_in_container") # Tested elsewhere
380
+ expect(host_report).to eq(
381
+ "architecture" => rbconfig["host_cpu"],
382
+ "os" => rbconfig["host_os"],
383
+ "language_version" => language_version,
384
+ "heroku" => false,
385
+ "root" => false
386
+ )
91
387
  end
92
388
 
93
389
  describe "root user detection" do
94
390
  context "when not root user" do
95
- it "prints no" do
391
+ it "outputs false" do
392
+ run
393
+ expect(output).to include "root user: false"
394
+ end
395
+
396
+ it "transmits root: false in report" do
96
397
  run
97
- expect(output).to include "root user: no"
398
+ expect(received_report["host"]["root"]).to eq(false)
98
399
  end
99
400
  end
100
401
 
@@ -104,19 +405,27 @@ describe Appsignal::CLI::Diagnose, :api_stub => true do
104
405
  run
105
406
  end
106
407
 
107
- it "prints yes, with warning" do
108
- expect(output).to include "root user: yes (not recommended)"
408
+ it "outputs true, with warning" do
409
+ expect(output).to include "root user: true (not recommended)"
410
+ end
411
+
412
+ it "transmits root: true in report" do
413
+ expect(received_report["host"]["root"]).to eq(true)
109
414
  end
110
415
  end
111
416
  end
112
417
 
113
418
  describe "Heroku detection" do
114
419
  context "when not on Heroku" do
115
- before { recognize_as_container(:none) { run } }
420
+ before { run }
116
421
 
117
422
  it "does not output Heroku detection" do
118
423
  expect(output).to_not include("Heroku:")
119
424
  end
425
+
426
+ it "transmits heroku: false in report" do
427
+ expect(received_report["host"]["heroku"]).to eq(false)
428
+ end
120
429
  end
121
430
 
122
431
  context "when on Heroku" do
@@ -125,24 +434,41 @@ describe Appsignal::CLI::Diagnose, :api_stub => true do
125
434
  it "outputs Heroku detection" do
126
435
  expect(output).to include("Heroku: true")
127
436
  end
437
+
438
+ it "transmits heroku: true in report" do
439
+ expect(received_report["host"]["heroku"]).to eq(true)
440
+ end
128
441
  end
129
442
  end
130
443
 
131
444
  describe "container detection" do
132
445
  context "when not in container" do
133
- before { recognize_as_container(:none) { run } }
446
+ before do
447
+ allow(Appsignal::Extension).to receive(:running_in_container?).and_return(false)
448
+ run
449
+ end
134
450
 
135
- it "does not output container detection" do
136
- expect(output).to_not include("Container id:")
451
+ it "outputs: false" do
452
+ expect(output).to include("Running in container: false")
453
+ end
454
+
455
+ it "transmits running_in_container: false in report" do
456
+ expect(received_report["host"]["running_in_container"]).to eq(false)
137
457
  end
138
458
  end
139
459
 
140
460
  context "when in container" do
141
- before { recognize_as_container(:docker) { run } }
461
+ before do
462
+ allow(Appsignal::Extension).to receive(:running_in_container?).and_return(true)
463
+ run
464
+ end
142
465
 
143
- it "outputs container information" do
144
- expect(output).to include \
145
- "Container id: 0c703b75cdeaad7c933aa68b4678cc5c37a12d5ef5d7cb52c9cefe684d98e575"
466
+ it "outputs: true" do
467
+ expect(output).to include("Running in container: true")
468
+ end
469
+
470
+ it "transmits running_in_container: true in report" do
471
+ expect(received_report["host"]["running_in_container"]).to eq(true)
146
472
  end
147
473
  end
148
474
  end
@@ -155,11 +481,10 @@ describe Appsignal::CLI::Diagnose, :api_stub => true do
155
481
  before do
156
482
  ENV.delete("RAILS_ENV") # From spec_helper
157
483
  ENV.delete("RACK_ENV")
158
- recognize_as_container(:none) { run_within_dir tmp_dir }
484
+ run_within_dir tmp_dir
159
485
  end
160
486
 
161
487
  it "outputs a warning that no config is loaded" do
162
- expect(output).to_not include "Error"
163
488
  expect(output).to include \
164
489
  "Environment: \n Warning: No environment set, no config loaded!",
165
490
  " appsignal diagnose --environment=production"
@@ -182,7 +507,6 @@ describe Appsignal::CLI::Diagnose, :api_stub => true do
182
507
 
183
508
  it "outputs configuration" do
184
509
  expect(output).to include("Configuration")
185
- expect(output).to_not include "Error"
186
510
  Appsignal.config.config_hash.each do |key, value|
187
511
  expect(output).to include("#{key}: #{value}")
188
512
  end
@@ -191,7 +515,7 @@ describe Appsignal::CLI::Diagnose, :api_stub => true do
191
515
 
192
516
  context "with unconfigured environment" do
193
517
  let(:config) { project_fixture_config("foobar") }
194
- before { recognize_as_container(:none) { run_within_dir tmp_dir } }
518
+ before { run_within_dir tmp_dir }
195
519
 
196
520
  it "outputs environment" do
197
521
  expect(output).to include("Environment: foobar")
@@ -199,7 +523,6 @@ describe Appsignal::CLI::Diagnose, :api_stub => true do
199
523
 
200
524
  it "outputs config defaults" do
201
525
  expect(output).to include("Configuration")
202
- expect(output).to_not include "Error"
203
526
  Appsignal::Config::DEFAULT_CONFIG.each do |key, value|
204
527
  expect(output).to include("#{key}: #{value}")
205
528
  end
@@ -215,7 +538,16 @@ describe Appsignal::CLI::Diagnose, :api_stub => true do
215
538
  end
216
539
 
217
540
  it "outputs valid" do
218
- expect(output).to include("Validating API key: Valid")
541
+ expect(output).to include "Validation",
542
+ "Validating Push API key: valid"
543
+ end
544
+
545
+ it "transmits validation in report" do
546
+ expect(received_report).to include(
547
+ "validation" => {
548
+ "push_api_key" => "valid"
549
+ }
550
+ )
219
551
  end
220
552
  end
221
553
 
@@ -226,7 +558,16 @@ describe Appsignal::CLI::Diagnose, :api_stub => true do
226
558
  end
227
559
 
228
560
  it "outputs invalid" do
229
- expect(output).to include("Validating API key: Invalid")
561
+ expect(output).to include "Validation",
562
+ "Validating Push API key: invalid"
563
+ end
564
+
565
+ it "transmits validation in report" do
566
+ expect(received_report).to include(
567
+ "validation" => {
568
+ "push_api_key" => "invalid"
569
+ }
570
+ )
230
571
  end
231
572
  end
232
573
 
@@ -237,7 +578,17 @@ describe Appsignal::CLI::Diagnose, :api_stub => true do
237
578
  end
238
579
 
239
580
  it "outputs failure with status code" do
240
- expect(output).to include("Validating API key: Failed with status 500")
581
+ expect(output).to include "Validation",
582
+ "Validating Push API key: Failed with status 500\n" +
583
+ %("Could not confirm authorization: 500")
584
+ end
585
+
586
+ it "transmits validation in report" do
587
+ expect(received_report).to include(
588
+ "validation" => {
589
+ "push_api_key" => %(Failed with status 500\n\"Could not confirm authorization: 500")
590
+ }
591
+ )
241
592
  end
242
593
  end
243
594
  end
@@ -250,17 +601,36 @@ describe Appsignal::CLI::Diagnose, :api_stub => true do
250
601
  end
251
602
  after { FileUtils.rm_rf([root_path, system_tmp_dir]) }
252
603
 
604
+ describe "report" do
605
+ let(:root_path) { tmp_dir }
606
+
607
+ it "adds paths to the report" do
608
+ run
609
+ expect(received_report["paths"].keys)
610
+ .to match_array(%w(root_path working_dir log_dir_path log_file_path))
611
+ end
612
+ end
613
+
253
614
  context "when a directory is not configured" do
254
615
  let(:root_path) { File.join(tmp_dir, "writable_path") }
255
616
  let(:config) { Appsignal::Config.new(root_path, "production", :log_file => nil) }
256
617
  before do
257
- FileUtils.mkdir_p(File.join(root_path, "log"), :mode => 0555)
258
- FileUtils.chmod(0555, system_tmp_dir)
618
+ FileUtils.mkdir_p(File.join(root_path, "log"), :mode => 0o555)
619
+ FileUtils.chmod(0o555, system_tmp_dir)
259
620
  run_within_dir root_path
260
621
  end
261
622
 
262
623
  it "outputs unconfigured directory" do
263
- expect(output).to include %(log_file_path: ""\n - Configured?: no)
624
+ expect(output).to include %(log_file_path: ""\n Configured?: false)
625
+ end
626
+
627
+ it "transmits path data in report" do
628
+ expect(received_report["paths"]["log_file_path"]).to eq(
629
+ "path" => nil,
630
+ "configured" => false,
631
+ "exists" => false,
632
+ "writable" => false
633
+ )
264
634
  end
265
635
  end
266
636
 
@@ -274,29 +644,46 @@ describe Appsignal::CLI::Diagnose, :api_stub => true do
274
644
  end
275
645
 
276
646
  it "outputs not existing path" do
277
- expect(output).to include %(root_path: "#{execution_path}"\n - Exists?: no)
647
+ expect(output).to include %(root_path: "#{execution_path}"\n Exists?: false)
648
+ end
649
+
650
+ it "transmits path data in report" do
651
+ expect(received_report["paths"]["root_path"]).to eq(
652
+ "path" => execution_path,
653
+ "configured" => true,
654
+ "exists" => false,
655
+ "writable" => false
656
+ )
278
657
  end
279
658
  end
280
659
 
281
660
  describe "ownership" do
661
+ let(:config) { Appsignal::Config.new(root_path, "production") }
662
+
282
663
  context "when a directory is owned by the current user" do
283
664
  let(:root_path) { File.join(tmp_dir, "owned_path") }
284
- let(:config) { Appsignal::Config.new(root_path, "production") }
285
- let(:process_user) { Etc.getpwuid(Process.uid).name }
286
665
  before { run_within_dir root_path }
287
666
 
288
667
  it "outputs ownership" do
289
668
  expect(output).to include \
290
- %(root_path: "#{root_path}"\n - Writable?: yes\n ) \
291
- "- Ownership?: yes (file: #{process_user}:#{Process.uid}, "\
669
+ %(root_path: "#{root_path}"\n Writable?: true\n ) \
670
+ "Ownership?: true (file: #{process_user}:#{Process.uid}, "\
292
671
  "process: #{process_user}:#{Process.uid})"
293
672
  end
673
+
674
+ it "transmits path data in report" do
675
+ expect(received_report["paths"]["root_path"]).to eq(
676
+ "path" => root_path,
677
+ "configured" => true,
678
+ "exists" => true,
679
+ "writable" => true,
680
+ "ownership" => { "uid" => Process.uid, "user" => process_user }
681
+ )
682
+ end
294
683
  end
295
684
 
296
685
  context "when a directory is not owned by the current user" do
297
686
  let(:root_path) { File.join(tmp_dir, "not_owned_path") }
298
- let(:config) { Appsignal::Config.new(root_path, "production") }
299
- let(:process_user) { Etc.getpwuid(Process.uid).name }
300
687
  before do
301
688
  stat = File.stat(root_path)
302
689
  allow(stat).to receive(:uid).and_return(0)
@@ -306,61 +693,91 @@ describe Appsignal::CLI::Diagnose, :api_stub => true do
306
693
 
307
694
  it "outputs no ownership" do
308
695
  expect(output).to include \
309
- %(root_path: "#{root_path}"\n - Writable?: yes\n ) \
310
- "- Ownership?: no (file: root:0, process: #{process_user}:#{Process.uid})"
696
+ %(root_path: "#{root_path}"\n Writable?: true\n ) \
697
+ "Ownership?: false (file: root:0, process: #{process_user}:#{Process.uid})"
311
698
  end
312
699
  end
313
700
  end
314
701
 
315
- describe "current_path" do
702
+ describe "working_dir" do
316
703
  let(:root_path) { tmp_dir }
317
704
  let(:config) { Appsignal::Config.new(root_path, "production") }
318
705
  before { run_within_dir root_path }
319
706
 
320
707
  it "outputs current path" do
321
- expect(output).to include %(current_path: "#{tmp_dir}"\n - Writable?: yes)
708
+ expect(output).to include %(working_dir: "#{tmp_dir}"\n Writable?: true)
709
+ end
710
+
711
+ it "transmits path data in report" do
712
+ expect(received_report["paths"]["working_dir"]).to eq(
713
+ "path" => tmp_dir,
714
+ "configured" => true,
715
+ "exists" => true,
716
+ "writable" => true,
717
+ "ownership" => { "uid" => Process.uid, "user" => process_user }
718
+ )
322
719
  end
323
720
  end
324
721
 
325
722
  describe "root_path" do
326
723
  let(:system_tmp_log_file) { File.join(system_tmp_dir, "appsignal.log") }
724
+ let(:config) { Appsignal::Config.new(root_path, "production") }
725
+
327
726
  context "when not writable" do
328
727
  let(:root_path) { File.join(tmp_dir, "not_writable_path") }
329
- let(:config) { Appsignal::Config.new(root_path, "production") }
330
728
  before do
331
- FileUtils.chmod(0555, root_path)
729
+ FileUtils.chmod(0o555, root_path)
332
730
  run_within_dir root_path
333
731
  end
334
732
 
335
733
  it "outputs not writable root path" do
336
- expect(output).to include %(root_path: "#{root_path}"\n - Writable?: no)
734
+ expect(output).to include %(root_path: "#{root_path}"\n Writable?: false)
337
735
  end
338
736
 
339
737
  it "log files fall back on system tmp directory" do
340
738
  expect(output).to include \
341
- %(log_dir_path: "#{system_tmp_dir}"\n - Writable?: yes),
342
- %(log_file_path: "#{system_tmp_log_file}"\n - Exists?: no)
739
+ %(log_dir_path: "#{system_tmp_dir}"\n Writable?: true),
740
+ %(log_file_path: "#{system_tmp_log_file}"\n Exists?: false)
741
+ end
742
+
743
+ it "transmits path data in report" do
744
+ expect(received_report["paths"]["root_path"]).to eq(
745
+ "path" => root_path,
746
+ "configured" => true,
747
+ "exists" => true,
748
+ "writable" => false,
749
+ "ownership" => { "uid" => Process.uid, "user" => process_user }
750
+ )
343
751
  end
344
752
  end
345
753
 
346
754
  context "when writable" do
347
755
  let(:root_path) { File.join(tmp_dir, "writable_path") }
348
- let(:config) { Appsignal::Config.new(root_path, "production") }
349
756
 
350
757
  context "without log dir" do
351
758
  before do
352
- FileUtils.chmod(0777, root_path)
759
+ FileUtils.chmod(0o777, root_path)
353
760
  run_within_dir root_path
354
761
  end
355
762
 
356
763
  it "outputs writable root path" do
357
- expect(output).to include %(root_path: "#{root_path}"\n - Writable?: yes)
764
+ expect(output).to include %(root_path: "#{root_path}"\n Writable?: true)
358
765
  end
359
766
 
360
767
  it "log files fall back on system tmp directory" do
361
768
  expect(output).to include \
362
- %(log_dir_path: "#{system_tmp_dir}"\n - Writable?: yes),
363
- %(log_file_path: "#{system_tmp_log_file}"\n - Exists?: no)
769
+ %(log_dir_path: "#{system_tmp_dir}"\n Writable?: true),
770
+ %(log_file_path: "#{system_tmp_log_file}"\n Exists?: false)
771
+ end
772
+
773
+ it "transmits path data in report" do
774
+ expect(received_report["paths"]["root_path"]).to eq(
775
+ "path" => root_path,
776
+ "configured" => true,
777
+ "exists" => true,
778
+ "writable" => true,
779
+ "ownership" => { "uid" => Process.uid, "user" => process_user }
780
+ )
364
781
  end
365
782
  end
366
783
 
@@ -371,14 +788,19 @@ describe Appsignal::CLI::Diagnose, :api_stub => true do
371
788
 
372
789
  context "when not writable" do
373
790
  before do
374
- FileUtils.chmod(0444, log_dir)
791
+ FileUtils.chmod(0o444, log_dir)
375
792
  run_within_dir root_path
376
793
  end
377
794
 
378
795
  it "log files fall back on system tmp directory" do
379
796
  expect(output).to include \
380
- %(log_dir_path: "#{system_tmp_dir}"\n - Writable?: yes),
381
- %(log_file_path: "#{system_tmp_log_file}"\n - Exists?: no)
797
+ %(log_dir_path: "#{system_tmp_dir}"\n Writable?: true),
798
+ %(log_file_path: "#{system_tmp_log_file}"\n Exists?: false)
799
+ end
800
+
801
+ it "transmits path data in report" do
802
+ expect(received_report["paths"]["log_dir_path"]).to be_kind_of(Hash)
803
+ expect(received_report["paths"]["log_file_path"]).to be_kind_of(Hash)
382
804
  end
383
805
  end
384
806
 
@@ -388,9 +810,14 @@ describe Appsignal::CLI::Diagnose, :api_stub => true do
388
810
 
389
811
  it "outputs writable but without log file" do
390
812
  expect(output).to include \
391
- %(root_path: "#{root_path}"\n - Writable?: yes),
392
- %(log_dir_path: "#{log_dir}"\n - Writable?: yes),
393
- %(log_file_path: "#{log_file}"\n - Exists?: no)
813
+ %(root_path: "#{root_path}"\n Writable?: true),
814
+ %(log_dir_path: "#{log_dir}"\n Writable?: true),
815
+ %(log_file_path: "#{log_file}"\n Exists?: false)
816
+ end
817
+
818
+ it "transmits path data in report" do
819
+ expect(received_report["paths"]["log_dir_path"]).to be_kind_of(Hash)
820
+ expect(received_report["paths"]["log_file_path"]).to be_kind_of(Hash)
394
821
  end
395
822
  end
396
823
 
@@ -402,19 +829,29 @@ describe Appsignal::CLI::Diagnose, :api_stub => true do
402
829
  end
403
830
 
404
831
  it "lists log file as writable" do
405
- expect(output).to include %(log_file_path: "#{log_file}"\n - Writable?: yes)
832
+ expect(output).to include %(log_file_path: "#{log_file}"\n Writable?: true)
833
+ end
834
+
835
+ it "transmits path data in report" do
836
+ expect(received_report["paths"]["log_dir_path"]).to be_kind_of(Hash)
837
+ expect(received_report["paths"]["log_file_path"]).to be_kind_of(Hash)
406
838
  end
407
839
  end
408
840
 
409
841
  context "when not writable" do
410
842
  before do
411
843
  FileUtils.touch(log_file)
412
- FileUtils.chmod(0444, log_file)
844
+ FileUtils.chmod(0o444, log_file)
413
845
  run_within_dir root_path
414
846
  end
415
847
 
416
848
  it "lists log file as not writable" do
417
- expect(output).to include %(log_file_path: "#{log_file}"\n - Writable?: no)
849
+ expect(output).to include %(log_file_path: "#{log_file}"\n Writable?: false)
850
+ end
851
+
852
+ it "transmits path data in report" do
853
+ expect(received_report["paths"]["log_dir_path"]).to be_kind_of(Hash)
854
+ expect(received_report["paths"]["log_file_path"]).to be_kind_of(Hash)
418
855
  end
419
856
  end
420
857
  end
@@ -429,36 +866,57 @@ describe Appsignal::CLI::Diagnose, :api_stub => true do
429
866
  let(:ext_path) { File.join(gem_path, "ext") }
430
867
  let(:log_path) { File.join(ext_path, log_file) }
431
868
  before do
869
+ FileUtils.mkdir_p ext_path
432
870
  allow(cli).to receive(:gem_path).and_return(gem_path)
433
871
  end
872
+ after { FileUtils.rm_rf ext_path }
434
873
 
435
874
  context "when file exists" do
436
875
  let(:gem_path) { File.join(tmp_dir, "gem") }
876
+ let(:log_content) do
877
+ [
878
+ "log line 1",
879
+ "log line 2"
880
+ ]
881
+ end
437
882
  before do
438
- FileUtils.mkdir_p ext_path
439
883
  File.open log_path, "a" do |f|
440
- f.write "log line 1"
441
- f.write "log line 2"
884
+ log_content.each do |line|
885
+ f.puts line
886
+ end
442
887
  end
443
888
  run
444
889
  end
445
890
 
446
891
  it "outputs install.log" do
447
- expect(output).to include \
448
- %(Path: "#{log_path}"),
449
- "log line 1",
450
- "log line 2"
892
+ expect(output).to include(%(Path: "#{log_path}"))
893
+ expect(output).to include(*log_content)
894
+ end
895
+
896
+ it "transmits log data in report" do
897
+ expect(received_report["logs"][File.join("ext", log_file)]).to eq(
898
+ "path" => log_path,
899
+ "exists" => true,
900
+ "content" => log_content
901
+ )
451
902
  end
452
903
 
453
904
  after { FileUtils.rm_rf(gem_path) }
454
905
  end
455
906
 
456
907
  context "when file does not exist" do
457
- let(:gem_path) { File.join(tmp_dir, "non_existent_path") }
908
+ let(:gem_path) { File.join(tmp_dir, "gem_without_log_files") }
458
909
  before { run }
459
910
 
460
911
  it "outputs install.log" do
461
- expect(output).to include %(Path: "#{log_path}"\n File not found.)
912
+ expect(output).to include %(Path: "#{log_path}"\n File not found.)
913
+ end
914
+
915
+ it "transmits log data in report" do
916
+ expect(received_report["logs"][File.join("ext", log_file)]).to eq(
917
+ "path" => log_path,
918
+ "exists" => false
919
+ )
462
920
  end
463
921
  end
464
922
  end
@@ -477,7 +935,7 @@ describe Appsignal::CLI::Diagnose, :api_stub => true do
477
935
 
478
936
  it "outputs header" do
479
937
  run
480
- expect(output).to include("Extension install log")
938
+ expect(output).to include("Makefile install log")
481
939
  end
482
940
  end
483
941
  end