appsignal 2.0.3 → 2.0.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,17 +1,22 @@
1
1
  require "appsignal/cli"
2
2
 
3
- describe Appsignal::CLI::Diagnose do
3
+ describe Appsignal::CLI::Diagnose, :api_stub => true do
4
4
  describe ".run" do
5
- let(:out_stream) { StringIO.new }
5
+ let(:out_stream) { std_stream }
6
+ let(:output) { out_stream.read }
6
7
  let(:config) { project_fixture_config }
7
8
  let(:cli) { described_class }
8
- let(:output) { out_stream.string }
9
+ let(:options) { { :environment => config.env } }
10
+ before(:all) { Appsignal.stop }
9
11
  before do
10
- ENV["APPSIGNAL_APP_ENV"] = "production"
12
+ if DependencyHelper.rails_present?
13
+ allow(Rails).to receive(:root).and_return(Pathname.new(config.root_path))
14
+ end
15
+ end
16
+ before :api_stub => true do
11
17
  stub_api_request config, "auth"
12
18
  end
13
19
  after { Appsignal.config = nil }
14
- around { |example| capture_stdout(out_stream) { example.run } }
15
20
 
16
21
  def run
17
22
  run_within_dir project_fixture_path
@@ -19,7 +24,7 @@ describe Appsignal::CLI::Diagnose do
19
24
 
20
25
  def run_within_dir(chdir)
21
26
  Dir.chdir chdir do
22
- cli.run
27
+ capture_stdout(out_stream) { cli.run(options) }
23
28
  end
24
29
  end
25
30
 
@@ -31,13 +36,48 @@ describe Appsignal::CLI::Diagnose do
31
36
  "support@appsignal.com"
32
37
  end
33
38
 
34
- it "outputs version numbers" do
35
- run
36
- gem_path = Bundler::CLI::Common.select_spec("appsignal").full_gem_path.strip
37
- expect(output).to include \
38
- "Gem version: #{Appsignal::VERSION}",
39
- "Agent version: #{Appsignal::Extension.agent_version}",
40
- "Gem install path: #{gem_path}"
39
+ describe "agent information" do
40
+ it "outputs version numbers" do
41
+ run
42
+ gem_path = Bundler::CLI::Common.select_spec("appsignal").full_gem_path.strip
43
+ expect(output).to include \
44
+ "Gem version: #{Appsignal::VERSION}",
45
+ "Agent version: #{Appsignal::Extension.agent_version}",
46
+ "Gem install path: #{gem_path}"
47
+ end
48
+
49
+ context "with extension" do
50
+ it "outputs extension is loaded" do
51
+ run
52
+ expect(output).to include "Extension loaded: yes"
53
+ end
54
+
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"
64
+ end
65
+ end
66
+
67
+ context "without extension" do
68
+ before do
69
+ # When the extension isn't loaded the Appsignal.start operation exits
70
+ # early and doesn't load the configuration.
71
+ # Happens when the extension wasn't installed properly.
72
+ Appsignal.extension_loaded = false
73
+ run
74
+ end
75
+ after { Appsignal.extension_loaded = true }
76
+
77
+ it "outputs extension is not loaded" do
78
+ expect(output).to include "Extension loaded: no"
79
+ end
80
+ end
41
81
  end
42
82
 
43
83
  describe "host information" do
@@ -50,6 +90,26 @@ describe Appsignal::CLI::Diagnose do
50
90
  "Ruby version: #{RbConfig::CONFIG["RUBY_VERSION_NAME"]}"
51
91
  end
52
92
 
93
+ describe "root user detection" do
94
+ context "when not root user" do
95
+ it "prints no" do
96
+ run
97
+ expect(output).to include "root user: no"
98
+ end
99
+ end
100
+
101
+ context "when root user" do
102
+ before do
103
+ allow(Process).to receive(:uid).and_return(0)
104
+ run
105
+ end
106
+
107
+ it "prints yes, with warning" do
108
+ expect(output).to include "root user: yes (not recommended)"
109
+ end
110
+ end
111
+ end
112
+
53
113
  describe "Heroku detection" do
54
114
  context "when not on Heroku" do
55
115
  before { recognize_as_container(:none) { run } }
@@ -89,42 +149,20 @@ describe Appsignal::CLI::Diagnose do
89
149
  end
90
150
 
91
151
  describe "configuration" do
92
- context "without extension" do
93
- before do
94
- # When the extension isn't loaded the Appsignal.start operation exits
95
- # early and doesn't load the configuration.
96
- # Happens when the extension wasn't installed properly.
97
- Appsignal.extension_loaded = false
98
- run
99
- end
100
- after { Appsignal.extension_loaded = true }
101
-
102
- it "outputs an error" do
103
- expect(output).to include \
104
- "Error: No config found!\nCould not start AppSignal."
105
- end
106
-
107
- it "outputs as much as it can" do
108
- expect(output).to include \
109
- "AppSignal agent\n Gem version: #{Appsignal::VERSION}",
110
- "Host information\n Architecture: ",
111
- %(Extension install log\n Path: "),
112
- %(Makefile install log\n Path: ")
113
- end
114
- end
115
-
116
152
  context "without environment" do
117
- let(:config) { project_fixture_config("") }
153
+ let(:config) { project_fixture_config(nil) }
154
+ let(:options) { {} }
118
155
  before do
119
- ENV["APPSIGNAL_APP_ENV"] = ""
120
- recognize_as_container(:none) { run }
156
+ ENV.delete("RAILS_ENV") # From spec_helper
157
+ ENV.delete("RACK_ENV")
158
+ recognize_as_container(:none) { run_within_dir tmp_dir }
121
159
  end
122
160
 
123
161
  it "outputs a warning that no config is loaded" do
124
162
  expect(output).to_not include "Error"
125
163
  expect(output).to include \
126
164
  "Environment: \n Warning: No environment set, no config loaded!",
127
- " APPSIGNAL_APP_ENV=production appsignal diagnose"
165
+ " appsignal diagnose --environment=production"
128
166
  end
129
167
 
130
168
  it "outputs config defaults" do
@@ -153,10 +191,7 @@ describe Appsignal::CLI::Diagnose do
153
191
 
154
192
  context "with unconfigured environment" do
155
193
  let(:config) { project_fixture_config("foobar") }
156
- before do
157
- ENV["APPSIGNAL_APP_ENV"] = "foobar"
158
- recognize_as_container(:none) { run }
159
- end
194
+ before { recognize_as_container(:none) { run_within_dir tmp_dir } }
160
195
 
161
196
  it "outputs environment" do
162
197
  expect(output).to include("Environment: foobar")
@@ -172,7 +207,7 @@ describe Appsignal::CLI::Diagnose do
172
207
  end
173
208
  end
174
209
 
175
- describe "API key validation" do
210
+ describe "API key validation", :api_stub => false do
176
211
  context "with valid key" do
177
212
  before do
178
213
  stub_api_request(config, "auth").to_return(:status => 200)
@@ -208,71 +243,185 @@ describe Appsignal::CLI::Diagnose do
208
243
  end
209
244
 
210
245
  describe "paths" do
211
- before { FileUtils.mkdir_p(root_path) }
246
+ let(:system_tmp_dir) { Appsignal::Config::SYSTEM_TMP_DIR }
247
+ before do
248
+ FileUtils.mkdir_p(root_path)
249
+ FileUtils.mkdir_p(system_tmp_dir)
250
+ end
251
+ after { FileUtils.rm_rf([root_path, system_tmp_dir]) }
212
252
 
213
- context "when a directory is writable" do
253
+ context "when a directory is not configured" do
214
254
  let(:root_path) { File.join(tmp_dir, "writable_path") }
215
- let(:log_file) { File.join(root_path, "appsignal.log") }
216
- let(:config) { Appsignal::Config.new(root_path, "production") }
255
+ let(:config) { Appsignal::Config.new(root_path, "production", :log_file => nil) }
256
+ before do
257
+ FileUtils.mkdir_p(File.join(root_path, "log"), :mode => 0555)
258
+ FileUtils.chmod(0555, system_tmp_dir)
259
+ run_within_dir root_path
260
+ end
261
+
262
+ it "outputs unconfigured directory" do
263
+ expect(output).to include %(log_file_path: ""\n - Configured?: no)
264
+ end
265
+ end
266
+
267
+ context "when a directory does not exist" do
268
+ let(:root_path) { tmp_dir }
269
+ let(:execution_path) { File.join(tmp_dir, "not_existing_dir") }
270
+ let(:config) { Appsignal::Config.new(execution_path, "production") }
271
+ before do
272
+ allow(Dir).to receive(:pwd).and_return(execution_path)
273
+ run_within_dir tmp_dir
274
+ end
217
275
 
218
- context "without log file" do
276
+ it "outputs not existing path" do
277
+ expect(output).to include %(root_path: "#{execution_path}"\n - Exists?: no)
278
+ end
279
+ end
280
+
281
+ describe "ownership" do
282
+ context "when a directory is owned by the current user" do
283
+ 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 }
219
286
  before { run_within_dir root_path }
220
287
 
221
- it "outputs writable" do
288
+ it "outputs ownership" do
222
289
  expect(output).to include \
223
- "Required paths",
224
- %(root_path: "#{root_path}" - Writable),
225
- %(log_file_path: "#{log_file}" - Does not exist)
290
+ %(root_path: "#{root_path}"\n - Writable?: yes\n ) \
291
+ "- Ownership?: yes (file: #{process_user}:#{Process.uid}, "\
292
+ "process: #{process_user}:#{Process.uid})"
226
293
  end
227
294
  end
228
295
 
229
- context "with log file" do
230
- context "when writable" do
231
- before do
232
- FileUtils.touch(log_file)
233
- run_within_dir root_path
234
- end
296
+ context "when a directory is not owned by the current user" do
297
+ 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
+ before do
301
+ stat = File.stat(root_path)
302
+ allow(stat).to receive(:uid).and_return(0)
303
+ allow(File).to receive(:stat).and_return(stat)
304
+ run_within_dir root_path
305
+ end
235
306
 
236
- it "lists log file as writable" do
237
- expect(output).to include \
238
- %(root_path: "#{root_path}" - Writable),
239
- %(log_file_path: "#{File.join(root_path, "appsignal.log")}" - Writable)
240
- end
307
+ it "outputs no ownership" do
308
+ expect(output).to include \
309
+ %(root_path: "#{root_path}"\n - Writable?: yes\n ) \
310
+ "- Ownership?: no (file: root:0, process: #{process_user}:#{Process.uid})"
241
311
  end
312
+ end
313
+ end
242
314
 
243
- context "when not writable" do
315
+ describe "current_path" do
316
+ let(:root_path) { tmp_dir }
317
+ let(:config) { Appsignal::Config.new(root_path, "production") }
318
+ before { run_within_dir root_path }
319
+
320
+ it "outputs current path" do
321
+ expect(output).to include %(current_path: "#{tmp_dir}"\n - Writable?: yes)
322
+ end
323
+ end
324
+
325
+ describe "root_path" do
326
+ let(:system_tmp_log_file) { File.join(system_tmp_dir, "appsignal.log") }
327
+ context "when not writable" do
328
+ let(:root_path) { File.join(tmp_dir, "not_writable_path") }
329
+ let(:config) { Appsignal::Config.new(root_path, "production") }
330
+ before do
331
+ FileUtils.chmod(0555, root_path)
332
+ run_within_dir root_path
333
+ end
334
+
335
+ it "outputs not writable root path" do
336
+ expect(output).to include %(root_path: "#{root_path}"\n - Writable?: no)
337
+ end
338
+
339
+ it "log files fall back on system tmp directory" do
340
+ expect(output).to include \
341
+ %(log_dir_path: "#{system_tmp_dir}"\n - Writable?: yes)
342
+ %(log_file_path: "#{system_tmp_log_file}"\n - Exist?: false)
343
+ end
344
+ end
345
+
346
+ context "when writable" do
347
+ let(:root_path) { File.join(tmp_dir, "writable_path") }
348
+ let(:config) { Appsignal::Config.new(root_path, "production") }
349
+
350
+ context "without log dir" do
244
351
  before do
245
- FileUtils.touch(log_file)
246
- FileUtils.chmod(0444, log_file)
352
+ FileUtils.chmod(0777, root_path)
247
353
  run_within_dir root_path
248
354
  end
249
355
 
250
- it "lists log file as not writable" do
356
+ it "outputs writable root path" do
357
+ expect(output).to include %(root_path: "#{root_path}"\n - Writable?: yes)
358
+ end
359
+
360
+ it "log files fall back on system tmp directory" do
251
361
  expect(output).to include \
252
- %(root_path: "#{root_path}" - Writable),
253
- %(log_file_path: "#{File.join(root_path, "appsignal.log")}" - Not writable)
362
+ %(log_dir_path: "#{system_tmp_dir}"\n - Writable?: yes),
363
+ %(log_file_path: "#{system_tmp_log_file}"\n - Exists?: no)
254
364
  end
255
365
  end
256
- end
257
- end
258
366
 
259
- context "when a directory is not writable" do
260
- let(:root_path) { File.join(tmp_dir, "not_writable_path") }
261
- let(:config) { Appsignal::Config.new(root_path, "production") }
262
- before do
263
- FileUtils.chmod(0555, root_path)
264
- run_within_dir root_path
265
- end
367
+ context "with log dir" do
368
+ let(:log_dir) { File.join(root_path, "log") }
369
+ let(:log_file) { File.join(log_dir, "appsignal.log") }
370
+ before { FileUtils.mkdir_p(log_dir) }
371
+
372
+ context "when not writable" do
373
+ before do
374
+ FileUtils.chmod(0444, log_dir)
375
+ run_within_dir root_path
376
+ end
377
+
378
+ it "log files fall back on system tmp directory" do
379
+ 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)
382
+ end
383
+ end
266
384
 
267
- it "outputs not writable" do
268
- expect(output).to include \
269
- "Required paths",
270
- %(root_path: "#{root_path}" - Not writable),
271
- %(log_file_path: "" - Not writable)
385
+ context "when writable" do
386
+ context "without log file" do
387
+ before { run_within_dir root_path }
388
+
389
+ it "outputs writable but without log file" do
390
+ 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)
394
+ end
395
+ end
396
+
397
+ context "with log file" do
398
+ context "when writable" do
399
+ before do
400
+ FileUtils.touch(log_file)
401
+ run_within_dir root_path
402
+ end
403
+
404
+ it "lists log file as writable" do
405
+ expect(output).to include %(log_file_path: "#{log_file}"\n - Writable?: yes)
406
+ end
407
+ end
408
+
409
+ context "when not writable" do
410
+ before do
411
+ FileUtils.touch(log_file)
412
+ FileUtils.chmod(0444, log_file)
413
+ run_within_dir root_path
414
+ end
415
+
416
+ it "lists log file as not writable" do
417
+ expect(output).to include %(log_file_path: "#{log_file}"\n - Writable?: no)
418
+ end
419
+ end
420
+ end
421
+ end
422
+ end
272
423
  end
273
424
  end
274
-
275
- after { FileUtils.rm_rf(root_path) }
276
425
  end
277
426
 
278
427
  describe "logs" do
@@ -0,0 +1,99 @@
1
+ require "appsignal/cli/helpers"
2
+
3
+ describe Appsignal::CLI::Helpers do
4
+ include CLIHelpers
5
+
6
+ let(:out_stream) { std_stream }
7
+ let(:output) { out_stream.read }
8
+ let(:cli) do
9
+ Class.new do
10
+ extend Appsignal::CLI::Helpers
11
+ end
12
+ end
13
+ before do
14
+ # Speed up tests
15
+ allow(cli).to receive(:sleep)
16
+ end
17
+ around do |example|
18
+ original_stdin = $stdin
19
+ $stdin = StringIO.new
20
+ example.run
21
+ $stdin = original_stdin
22
+ end
23
+
24
+ describe ".colorize" do
25
+ subject { cli.send(:colorize, "text", :green) }
26
+
27
+ context "on windows" do
28
+ before { allow(Gem).to receive(:win_platform?).and_return(true) }
29
+
30
+ it "outputs plain string" do
31
+ expect(subject).to eq "text"
32
+ end
33
+ end
34
+
35
+ context "not on windows" do
36
+ before { allow(Gem).to receive(:win_platform?).and_return(false) }
37
+
38
+ it "wraps text in color tags" do
39
+ expect(subject).to eq "\e[32mtext\e[0m"
40
+ end
41
+ end
42
+ end
43
+
44
+ describe ".periods" do
45
+ it "prints three periods" do
46
+ capture_stdout(out_stream) { cli.send :periods }
47
+ expect(output).to include("...")
48
+ end
49
+ end
50
+
51
+ describe ".press_any_key" do
52
+ before do
53
+ set_input "a" # a as in any
54
+ end
55
+
56
+ it "continues after press" do
57
+ capture_stdout(out_stream) { cli.send :press_any_key }
58
+ expect(output).to include("Press any key")
59
+ end
60
+ end
61
+
62
+ describe ".yes_or_no" do
63
+ def yes_or_no
64
+ capture_stdout(out_stream) { cli.send(:yes_or_no, "yes or no?: ") }
65
+ end
66
+
67
+ it "takes yes for an answer" do
68
+ set_input ""
69
+ set_input "nonsense"
70
+ set_input "y"
71
+ prepare_input
72
+
73
+ expect(yes_or_no).to be_true
74
+ end
75
+
76
+ it "takes no for an answer" do
77
+ set_input ""
78
+ set_input "nonsense"
79
+ set_input "n"
80
+ prepare_input
81
+
82
+ expect(yes_or_no).to be_false
83
+ end
84
+ end
85
+
86
+ describe ".required_input" do
87
+ def required_input
88
+ capture_stdout(out_stream) { cli.send(:required_input, "provide: ") }
89
+ end
90
+
91
+ it "collects required input" do
92
+ set_input ""
93
+ set_input "value"
94
+ prepare_input
95
+
96
+ expect(required_input).to eq("value")
97
+ end
98
+ end
99
+ end