appsignal 1.4.0.beta.1 → 2.0.0.beta.1

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.
@@ -2,9 +2,15 @@ module Appsignal
2
2
  class CLI
3
3
  class NotifyOfDeploy
4
4
  class << self
5
- def run(options, config)
5
+ def run(options)
6
+ config = config_for(options[:environment])
7
+ config[:name] = options[:name] if options[:name]
8
+
6
9
  validate_active_config(config)
7
- validate_required_options(options, [:revision, :user, :environment])
10
+ required_config = [:revision, :user]
11
+ required_config << :environment if config.env.empty?
12
+ required_config << :name if !config[:name] || config[:name].empty?
13
+ validate_required_options(options, required_config)
8
14
 
9
15
  Appsignal::Marker.new(
10
16
  {
@@ -15,24 +21,33 @@ module Appsignal
15
21
  ).transmit
16
22
  end
17
23
 
18
- protected
24
+ private
19
25
 
20
26
  def validate_required_options(options, required_options)
21
27
  missing = required_options.select do |required_option|
22
28
  val = options[required_option]
23
29
  val.nil? || (val.respond_to?(:empty?) && val.empty?)
24
30
  end
25
- if missing.any?
26
- puts "Missing options: #{missing.join(', ')}"
27
- exit 1
28
- end
31
+ return unless missing.any?
32
+
33
+ puts "Error: Missing options: #{missing.join(', ')}"
34
+ exit 1
29
35
  end
30
36
 
31
37
  def validate_active_config(config)
32
- unless config.active?
33
- puts 'Exiting: No config file or push api key env var found'
34
- exit 1
35
- end
38
+ return if config.active?
39
+
40
+ puts "Error: No valid config found."
41
+ exit 1
42
+ end
43
+
44
+ def config_for(environment)
45
+ Appsignal::Config.new(
46
+ Dir.pwd,
47
+ environment,
48
+ {},
49
+ Logger.new(StringIO.new)
50
+ )
36
51
  end
37
52
  end
38
53
  end
@@ -8,6 +8,7 @@ module Appsignal
8
8
  SYSTEM_TMP_DIR = '/tmp'
9
9
  DEFAULT_CONFIG = {
10
10
  :debug => false,
11
+ :log => 'file',
11
12
  :ignore_errors => [],
12
13
  :ignore_actions => [],
13
14
  :filter_parameters => [],
@@ -35,6 +36,7 @@ module Appsignal
35
36
  'APPSIGNAL_PUSH_API_ENDPOINT' => :endpoint,
36
37
  'APPSIGNAL_FRONTEND_ERROR_CATCHING_PATH' => :frontend_error_catching_path,
37
38
  'APPSIGNAL_DEBUG' => :debug,
39
+ 'APPSIGNAL_LOG' => :log,
38
40
  'APPSIGNAL_LOG_PATH' => :log_path,
39
41
  'APPSIGNAL_INSTRUMENT_NET_HTTP' => :instrument_net_http,
40
42
  'APPSIGNAL_INSTRUMENT_REDIS' => :instrument_redis,
@@ -96,7 +98,7 @@ module Appsignal
96
98
  if File.writable? SYSTEM_TMP_DIR
97
99
  $stdout.puts "appsignal: Unable to log to '#{path}'. Logging to "\
98
100
  "'#{SYSTEM_TMP_DIR}' instead. Please check the "\
99
- "permissions for the application's log directory."
101
+ "permissions for the application's (log) directory."
100
102
  File.join(SYSTEM_TMP_DIR, 'appsignal.log')
101
103
  else
102
104
  $stdout.puts "appsignal: Unable to log to '#{path}' or the "\
@@ -119,7 +121,7 @@ module Appsignal
119
121
  ENV['APPSIGNAL_AGENT_PATH'] = File.expand_path('../../../ext', __FILE__).to_s
120
122
  ENV['APPSIGNAL_ENVIRONMENT'] = env
121
123
  ENV['APPSIGNAL_AGENT_VERSION'] = Appsignal::Extension.agent_version
122
- ENV['APPSIGNAL_LANGUAGE_INTEGRATION_VERSION'] = Appsignal::VERSION
124
+ ENV['APPSIGNAL_LANGUAGE_INTEGRATION_VERSION'] = "ruby-#{Appsignal::VERSION}"
123
125
  ENV['APPSIGNAL_DEBUG_LOGGING'] = config_hash[:debug].to_s
124
126
  ENV['APPSIGNAL_LOG_FILE_PATH'] = log_file_path.to_s if log_file_path
125
127
  ENV['APPSIGNAL_PUSH_API_ENDPOINT'] = config_hash[:endpoint]
@@ -146,7 +148,8 @@ module Appsignal
146
148
  end
147
149
 
148
150
  def detect_from_system
149
- self[:running_in_container] = true if Appsignal::System.container?
151
+ config_hash[:running_in_container] = true if Appsignal::System.container?
152
+ config_hash[:log] = 'stdout' if Appsignal::System.heroku?
150
153
  end
151
154
 
152
155
  def load_from_disk
@@ -184,8 +187,9 @@ module Appsignal
184
187
 
185
188
  # Configuration with string type
186
189
  %w(APPSIGNAL_PUSH_API_KEY APPSIGNAL_APP_NAME APPSIGNAL_PUSH_API_ENDPOINT
187
- APPSIGNAL_FRONTEND_ERROR_CATCHING_PATH APPSIGNAL_HTTP_PROXY APPSIGNAL_LOG_PATH
188
- APPSIGNAL_WORKING_DIR_PATH APPSIGNAL_HOSTNAME APPSIGNAL_CA_FILE_PATH).each do |var|
190
+ APPSIGNAL_FRONTEND_ERROR_CATCHING_PATH APPSIGNAL_HTTP_PROXY
191
+ APPSIGNAL_LOG APPSIGNAL_LOG_PATH APPSIGNAL_WORKING_DIR_PATH
192
+ APPSIGNAL_HOSTNAME APPSIGNAL_CA_FILE_PATH).each do |var|
189
193
  if env_var = ENV[var]
190
194
  config[ENV_TO_KEY_MAPPING[var]] = env_var
191
195
  end
@@ -1,41 +1,48 @@
1
1
  module Appsignal
2
2
  module Grape
3
3
  class Middleware < ::Grape::Middleware::Base
4
- def initialize(app)
5
- @app = app
6
- end
7
-
8
4
  def call(env)
9
5
  if Appsignal.active?
10
6
  call_with_appsignal_monitoring(env)
11
7
  else
12
- @app.call(env)
8
+ app.call(env)
13
9
  end
14
10
  end
15
11
 
16
12
  def call_with_appsignal_monitoring(env)
17
- request = ::Rack::Request.new(env)
18
- transaction = Appsignal::Transaction.create(
13
+ request = ::Rack::Request.new(env)
14
+ transaction = Appsignal::Transaction.create(
19
15
  SecureRandom.uuid,
20
16
  Appsignal::Transaction::HTTP_REQUEST,
21
17
  request
22
18
  )
23
19
  begin
24
- @app.call(env)
20
+ app.call(env)
25
21
  rescue => error
26
22
  transaction.set_error(error)
27
23
  raise error
28
24
  ensure
29
- api_endpoint = env['api.endpoint']
30
- if api_endpoint && options = api_endpoint.options
31
- method = options[:method].first
32
- klass = options[:for]
33
- action = options[:path].first
34
- transaction.set_action("#{method}::#{klass}##{action}")
25
+ request_method = request.request_method
26
+ path = request.path # Path without namespaces
27
+ endpoint = env["api.endpoint"]
28
+
29
+ if endpoint && endpoint.options
30
+ options = endpoint.options
31
+ request_method = options[:method].first
32
+ klass = options[:for]
33
+ namespace = endpoint.namespace
34
+ namespace = "" if namespace == "/"
35
+
36
+ path = options[:path].first.to_s
37
+ path = "/#{path}" if path[0] != "/"
38
+ path = "#{namespace}#{path}"
39
+
40
+ transaction.set_action("#{request_method}::#{klass}##{path}")
35
41
  end
42
+
36
43
  transaction.set_http_or_background_queue_start
37
- transaction.set_metadata('path', request.path)
38
- transaction.set_metadata('method', env['REQUEST_METHOD'])
44
+ transaction.set_metadata("path", path)
45
+ transaction.set_metadata("method", request_method)
39
46
  Appsignal::Transaction.complete_current!
40
47
  end
41
48
  end
@@ -14,7 +14,7 @@ module Appsignal
14
14
  end
15
15
 
16
16
  def set_action
17
- @ext.set_action(@data['action'])
17
+ @ext.set_action(@data['action']) if @data['action']
18
18
  end
19
19
 
20
20
  def set_metadata
@@ -26,8 +26,8 @@ module Appsignal
26
26
  def set_error
27
27
  @ext.set_error(
28
28
  @data['name'],
29
- @data['message'],
30
- Appsignal::Utils.data_generate(@data['backtrace'])
29
+ @data['message'] || '',
30
+ Appsignal::Utils.data_generate(@data['backtrace'] || [])
31
31
  )
32
32
  end
33
33
 
@@ -10,16 +10,17 @@ module Appsignal
10
10
 
11
11
  def transmit
12
12
  transmitter = Transmitter.new(ACTION, config)
13
- puts "Notifying Appsignal of deploy with: "\
13
+ puts "Notifying AppSignal of deploy with: "\
14
14
  "revision: #{marker_data[:revision]}, user: #{marker_data[:user]}"
15
+
15
16
  result = transmitter.transmit(marker_data)
16
17
  if result == '200'
17
- puts 'Appsignal has been notified of this deploy!'
18
+ puts 'AppSignal has been notified of this deploy!'
18
19
  else
19
20
  raise "#{result} at #{transmitter.uri}"
20
21
  end
21
22
  rescue => e
22
- puts "Something went wrong while trying to notify Appsignal: #{e}"
23
+ puts "Something went wrong while trying to notify AppSignal: #{e}"
23
24
  end
24
25
  end
25
26
  end
@@ -8,11 +8,18 @@ module Appsignal
8
8
 
9
9
  def call(env)
10
10
  if env['PATH_INFO'] == Appsignal.config[:frontend_error_catching_path]
11
- body = JSON.parse(env['rack.input'].read)
12
- transaction = JSExceptionTransaction.new(body)
13
- transaction.complete!
11
+ body = JSON.parse(env['rack.input'].read)
14
12
 
15
- return [ 200, {}, []]
13
+ if body['name'].is_a?(String) && body['name'].length > 0
14
+ transaction = JSExceptionTransaction.new(body)
15
+ transaction.complete!
16
+ code = 200
17
+ else
18
+ Appsignal.logger.debug "JSExceptionCatcher: Could not send exception, 'name' is empty."
19
+ code = 422
20
+ end
21
+
22
+ return [code, {}, []]
16
23
  else
17
24
  @app.call(env)
18
25
  end
@@ -3,17 +3,6 @@ require 'appsignal/utils/query_params_sanitizer'
3
3
 
4
4
  module Appsignal
5
5
  module Utils
6
- module ClassMethods
7
- extend Gem::Deprecate
8
-
9
- def sanitize(params, only_top_level = false, key_sanitizer = nil)
10
- QueryParamsSanitizer.sanitize(params, only_top_level, key_sanitizer)
11
- end
12
-
13
- deprecate :sanitize, "AppSignal::Utils::QueryParamsSanitizer.sanitize", 2016, 9
14
- end
15
- extend ClassMethods
16
-
17
6
  def self.data_generate(body)
18
7
  Data.generate(body)
19
8
  end
@@ -126,5 +115,4 @@ module Appsignal
126
115
  end
127
116
  end
128
117
  end
129
-
130
118
  end
@@ -1,5 +1,5 @@
1
1
  require 'yaml'
2
2
 
3
3
  module Appsignal
4
- VERSION = '1.4.0.beta.1'
4
+ VERSION = '2.0.0.beta.1'
5
5
  end
@@ -134,8 +134,8 @@ if DependencyHelper.capistrano2_present?
134
134
  capistrano_config.find_and_execute_task('appsignal:deploy')
135
135
 
136
136
  expect(out_stream.string).to include \
137
- 'Notifying Appsignal of deploy with: revision: 503ce0923ed177a3ce000005, user: batman',
138
- 'Appsignal has been notified of this deploy!'
137
+ 'Notifying AppSignal of deploy with: revision: 503ce0923ed177a3ce000005, user: batman',
138
+ 'AppSignal has been notified of this deploy!'
139
139
  end
140
140
 
141
141
  context "with overridden revision" do
@@ -147,8 +147,8 @@ if DependencyHelper.capistrano2_present?
147
147
 
148
148
  it "transmits the overriden revision" do
149
149
  expect(out_stream.string).to include \
150
- 'Notifying Appsignal of deploy with: revision: abc123, user: batman',
151
- 'Appsignal has been notified of this deploy!'
150
+ 'Notifying AppSignal of deploy with: revision: abc123, user: batman',
151
+ 'AppSignal has been notified of this deploy!'
152
152
  end
153
153
  end
154
154
 
@@ -161,9 +161,9 @@ if DependencyHelper.capistrano2_present?
161
161
  it "does not transmit marker" do
162
162
  output = out_stream.string
163
163
  expect(output).to include \
164
- 'Notifying Appsignal of deploy with: revision: 503ce0923ed177a3ce000005, user: batman',
165
- 'Something went wrong while trying to notify Appsignal:'
166
- expect(output).to_not include 'Appsignal has been notified of this deploy!'
164
+ 'Notifying AppSignal of deploy with: revision: 503ce0923ed177a3ce000005, user: batman',
165
+ 'Something went wrong while trying to notify AppSignal:'
166
+ expect(output).to_not include 'AppSignal has been notified of this deploy!'
167
167
  end
168
168
  end
169
169
 
@@ -135,8 +135,8 @@ if DependencyHelper.capistrano3_present?
135
135
  invoke('appsignal:deploy')
136
136
 
137
137
  expect(out_stream.string).to include \
138
- 'Notifying Appsignal of deploy with: revision: 503ce0923ed177a3ce000005, user: batman',
139
- 'Appsignal has been notified of this deploy!'
138
+ 'Notifying AppSignal of deploy with: revision: 503ce0923ed177a3ce000005, user: batman',
139
+ 'AppSignal has been notified of this deploy!'
140
140
  end
141
141
 
142
142
  context "with overridden revision" do
@@ -148,8 +148,8 @@ if DependencyHelper.capistrano3_present?
148
148
 
149
149
  it "transmits the overriden revision" do
150
150
  expect(out_stream.string).to include \
151
- 'Notifying Appsignal of deploy with: revision: abc123, user: batman',
152
- 'Appsignal has been notified of this deploy!'
151
+ 'Notifying AppSignal of deploy with: revision: abc123, user: batman',
152
+ 'AppSignal has been notified of this deploy!'
153
153
  end
154
154
  end
155
155
 
@@ -162,9 +162,9 @@ if DependencyHelper.capistrano3_present?
162
162
  it "does not transmit marker" do
163
163
  output = out_stream.string
164
164
  expect(output).to include \
165
- 'Notifying Appsignal of deploy with: revision: 503ce0923ed177a3ce000005, user: batman',
166
- 'Something went wrong while trying to notify Appsignal:'
167
- expect(output).to_not include 'Appsignal has been notified of this deploy!'
165
+ 'Notifying AppSignal of deploy with: revision: 503ce0923ed177a3ce000005, user: batman',
166
+ 'Something went wrong while trying to notify AppSignal:'
167
+ expect(output).to_not include 'AppSignal has been notified of this deploy!'
168
168
  end
169
169
  end
170
170
  end
@@ -1,29 +1,336 @@
1
- require 'appsignal/cli'
1
+ require "appsignal/cli"
2
2
 
3
3
  describe Appsignal::CLI::Diagnose do
4
- let(:out_stream) { StringIO.new }
5
- let(:config) { project_fixture_config }
6
- let(:cli) { described_class }
7
- around do |example|
8
- capture_stdout(out_stream) { example.run }
9
- end
10
-
11
4
  describe ".run" do
5
+ let(:out_stream) { StringIO.new }
6
+ let(:config) { project_fixture_config }
7
+ let(:cli) { described_class }
8
+ let(:output) { out_stream.string }
12
9
  before do
13
- Appsignal.config = config
14
- stub_api_request config, 'auth'
15
- end
16
-
17
- it "should output diagnostic information" do
18
- cli.run
19
- output = out_stream.string
20
- expect(output).to include('Gem version')
21
- expect(output).to include('Agent version')
22
- expect(output).to include('Environment')
23
- expect(output).to include('Config')
24
- expect(output).to include('Checking API key')
25
- expect(output).to include('Checking if required paths are writable')
26
- expect(output).to include('Showing last lines of extension install log')
10
+ ENV["APPSIGNAL_APP_ENV"] = "production"
11
+ stub_api_request config, "auth"
12
+ end
13
+ after { Appsignal.config = nil }
14
+ around { |example| capture_stdout(out_stream) { example.run } }
15
+
16
+ def run
17
+ run_within_dir project_fixture_path
18
+ end
19
+
20
+ def run_within_dir(chdir)
21
+ Dir.chdir chdir do
22
+ cli.run
23
+ end
24
+ end
25
+
26
+ it "outputs header and support text" do
27
+ run
28
+ expect(output).to include \
29
+ "AppSignal diagnose",
30
+ "http://docs.appsignal.com/",
31
+ "support@appsignal.com"
32
+ end
33
+
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}"
41
+ end
42
+
43
+ describe "host information" do
44
+ it "outputs host information" do
45
+ run
46
+ expect(output).to include \
47
+ "Host information",
48
+ "Architecture: #{RbConfig::CONFIG["host_cpu"]}",
49
+ "Operating System: #{RbConfig::CONFIG["host_os"]}",
50
+ "Ruby version: #{RbConfig::CONFIG["RUBY_VERSION_NAME"]}"
51
+ end
52
+
53
+ describe "Heroku detection" do
54
+ context "when not on Heroku" do
55
+ before { recognize_as_container(:none) { run } }
56
+
57
+ it "does not output Heroku detection" do
58
+ expect(output).to_not include("Heroku:")
59
+ end
60
+ end
61
+
62
+ context "when on Heroku" do
63
+ before { recognize_as_heroku { run } }
64
+
65
+ it "outputs Heroku detection" do
66
+ expect(output).to include("Heroku: true")
67
+ end
68
+ end
69
+ end
70
+
71
+ describe "container detection" do
72
+ context "when not in container" do
73
+ before { recognize_as_container(:none) { run } }
74
+
75
+ it "does not output container detection" do
76
+ expect(output).to_not include("Container id:")
77
+ end
78
+ end
79
+
80
+ context "when in container" do
81
+ before { recognize_as_container(:docker) { run } }
82
+
83
+ it "outputs container information" do
84
+ expect(output).to include \
85
+ "Container id: 0c703b75cdeaad7c933aa68b4678cc5c37a12d5ef5d7cb52c9cefe684d98e575"
86
+ end
87
+ end
88
+ end
89
+ end
90
+
91
+ 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
+ context "without environment" do
117
+ let(:config) { project_fixture_config("") }
118
+ before do
119
+ ENV["APPSIGNAL_APP_ENV"] = ""
120
+ recognize_as_container(:none) { run }
121
+ end
122
+
123
+ it "outputs a warning that no config is loaded" do
124
+ expect(output).to_not include "Error"
125
+ expect(output).to include \
126
+ "Environment: \n Warning: No environment set, no config loaded!",
127
+ " APPSIGNAL_APP_ENV=production appsignal diagnose"
128
+ end
129
+
130
+ it "outputs config defaults" do
131
+ expect(output).to include("Configuration")
132
+ Appsignal::Config::DEFAULT_CONFIG.each do |key, value|
133
+ expect(output).to include("#{key}: #{value}")
134
+ end
135
+ end
136
+ end
137
+
138
+ context "with configured environment" do
139
+ before { run }
140
+
141
+ it "outputs environment" do
142
+ expect(output).to include("Environment: production")
143
+ end
144
+
145
+ it "outputs configuration" do
146
+ expect(output).to include("Configuration")
147
+ expect(output).to_not include "Error"
148
+ Appsignal.config.config_hash.each do |key, value|
149
+ expect(output).to include("#{key}: #{value}")
150
+ end
151
+ end
152
+ end
153
+
154
+ context "with unconfigured environment" do
155
+ let(:config) { project_fixture_config("foobar") }
156
+ before do
157
+ ENV["APPSIGNAL_APP_ENV"] = "foobar"
158
+ recognize_as_container(:none) { run }
159
+ end
160
+
161
+ it "outputs environment" do
162
+ expect(output).to include("Environment: foobar")
163
+ end
164
+
165
+ it "outputs config defaults" do
166
+ expect(output).to include("Configuration")
167
+ expect(output).to_not include "Error"
168
+ Appsignal::Config::DEFAULT_CONFIG.each do |key, value|
169
+ expect(output).to include("#{key}: #{value}")
170
+ end
171
+ end
172
+ end
173
+ end
174
+
175
+ describe "API key validation" do
176
+ context "with valid key" do
177
+ before do
178
+ stub_api_request(config, "auth").to_return(:status => 200)
179
+ run
180
+ end
181
+
182
+ it "outputs valid" do
183
+ expect(output).to include("Validating API key: Valid")
184
+ end
185
+ end
186
+
187
+ context "with invalid key" do
188
+ before do
189
+ stub_api_request(config, "auth").to_return(:status => 401)
190
+ run
191
+ end
192
+
193
+ it "outputs invalid" do
194
+ expect(output).to include("Validating API key: Invalid")
195
+ end
196
+ end
197
+
198
+ context "with invalid key" do
199
+ before do
200
+ stub_api_request(config, "auth").to_return(:status => 500)
201
+ run
202
+ end
203
+
204
+ it "outputs failure with status code" do
205
+ expect(output).to include("Validating API key: Failed with status 500")
206
+ end
207
+ end
208
+ end
209
+
210
+ describe "paths" do
211
+ before { FileUtils.mkdir_p(root_path) }
212
+
213
+ context "when a directory is writable" do
214
+ 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") }
217
+
218
+ context "without log file" do
219
+ before { run_within_dir root_path }
220
+
221
+ it "outputs writable" do
222
+ expect(output).to include \
223
+ "Required paths",
224
+ %(root_path: "#{root_path}" - Writable),
225
+ %(log_file_path: "#{log_file}" - Does not exist)
226
+ end
227
+ end
228
+
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
235
+
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
241
+ end
242
+
243
+ context "when not writable" do
244
+ before do
245
+ FileUtils.touch(log_file)
246
+ FileUtils.chmod(0444, log_file)
247
+ run_within_dir root_path
248
+ end
249
+
250
+ it "lists log file as not writable" do
251
+ expect(output).to include \
252
+ %(root_path: "#{root_path}" - Writable),
253
+ %(log_file_path: "#{File.join(root_path, "appsignal.log")}" - Not writable)
254
+ end
255
+ end
256
+ end
257
+ end
258
+
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
266
+
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)
272
+ end
273
+ end
274
+
275
+ after { FileUtils.rm_rf(root_path) }
276
+ end
277
+
278
+ describe "logs" do
279
+ shared_examples "ext log file" do |log_file|
280
+ let(:ext_path) { File.join(gem_path, "ext") }
281
+ let(:log_path) { File.join(ext_path, log_file) }
282
+ before do
283
+ allow(cli).to receive(:gem_path).and_return(gem_path)
284
+ end
285
+
286
+ context "when file exists" do
287
+ let(:gem_path) { File.join(tmp_dir, "gem") }
288
+ before do
289
+ FileUtils.mkdir_p ext_path
290
+ File.open log_path, "a" do |f|
291
+ f.write "log line 1"
292
+ f.write "log line 2"
293
+ end
294
+ run
295
+ end
296
+
297
+ it "outputs install.log" do
298
+ expect(output).to include \
299
+ %(Path: "#{log_path}"),
300
+ "log line 1",
301
+ "log line 2"
302
+ end
303
+
304
+ after { FileUtils.rm_rf(gem_path) }
305
+ end
306
+
307
+ context "when file does not exist" do
308
+ let(:gem_path) { File.join(tmp_dir, "non_existent_path") }
309
+ before { run }
310
+
311
+ it "outputs install.log" do
312
+ expect(output).to include %(Path: "#{log_path}"\n File not found.)
313
+ end
314
+ end
315
+ end
316
+
317
+ describe "install.log" do
318
+ it_behaves_like "ext log file", "install.log"
319
+
320
+ it "outputs header" do
321
+ run
322
+ expect(output).to include("Extension install log")
323
+ end
324
+ end
325
+
326
+ describe "mkmf.log" do
327
+ it_behaves_like "ext log file", "mkmf.log"
328
+
329
+ it "outputs header" do
330
+ run
331
+ expect(output).to include("Extension install log")
332
+ end
333
+ end
27
334
  end
28
335
  end
29
336
  end